pinggy 0.4.5 → 0.4.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 +92 -4
- package/dist/{chunk-HUN2MRZO.js → chunk-3RTRUYNW.js} +3 -1
- package/dist/{chunk-MBN3YBO4.js → chunk-443UO6IY.js} +204 -41
- package/dist/index.cjs +1025 -167
- package/dist/index.d.cts +20 -11
- package/dist/index.d.ts +20 -11
- package/dist/index.js +10 -7
- package/dist/{main-2RDHMQT7.js → main-PUM4SD6B.js} +771 -114
- package/dist/workers/file_serve_worker.js +1 -1
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -116,6 +116,11 @@ var init_printer = __esm({
|
|
|
116
116
|
const def = this.errorDefinitions.find((d) => d.match(err));
|
|
117
117
|
const msg = def.message(err);
|
|
118
118
|
console.error(import_picocolors2.default.red(import_picocolors2.default.bold("\u2716 Error:")), import_picocolors2.default.red(msg));
|
|
119
|
+
}
|
|
120
|
+
static fatal(err) {
|
|
121
|
+
const def = this.errorDefinitions.find((d) => d.match(err));
|
|
122
|
+
const msg = def.message(err);
|
|
123
|
+
console.error(import_picocolors2.default.red(import_picocolors2.default.bold("\u2716 Fatal Error:")), import_picocolors2.default.red(msg));
|
|
119
124
|
process.exit(1);
|
|
120
125
|
}
|
|
121
126
|
static red(message) {
|
|
@@ -254,7 +259,9 @@ function enablePackageLogging(opts) {
|
|
|
254
259
|
return applyLoggingConfig(opts ?? {});
|
|
255
260
|
}
|
|
256
261
|
function enableLoggingByLogLevelInSdk(loglevel, logFilePath) {
|
|
257
|
-
if (!loglevel)
|
|
262
|
+
if (!loglevel) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
258
265
|
const l = loglevel.toUpperCase();
|
|
259
266
|
if (loglevel === "DEBUG") {
|
|
260
267
|
import_pinggy.pinggy.setDebugLogging(true, import_pinggy.LogLevel.DEBUG, logFilePath);
|
|
@@ -430,7 +437,9 @@ var init_TunnelManager = __esm({
|
|
|
430
437
|
*/
|
|
431
438
|
async startTunnel(tunnelId) {
|
|
432
439
|
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
433
|
-
if (!managed)
|
|
440
|
+
if (!managed) {
|
|
441
|
+
throw new Error(`Tunnel with id "${tunnelId}" not found`);
|
|
442
|
+
}
|
|
434
443
|
logger.info("Starting tunnel", { tunnelId });
|
|
435
444
|
let urls;
|
|
436
445
|
try {
|
|
@@ -478,7 +487,9 @@ var init_TunnelManager = __esm({
|
|
|
478
487
|
*/
|
|
479
488
|
stopTunnel(tunnelId) {
|
|
480
489
|
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
481
|
-
if (!managed)
|
|
490
|
+
if (!managed) {
|
|
491
|
+
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
492
|
+
}
|
|
482
493
|
logger.info("Stopping tunnel", { tunnelId, configId: managed.configId });
|
|
483
494
|
try {
|
|
484
495
|
managed.instance.stop();
|
|
@@ -659,12 +670,16 @@ var init_TunnelManager = __esm({
|
|
|
659
670
|
getTunnelInstance(configId, tunnelId) {
|
|
660
671
|
if (configId) {
|
|
661
672
|
const managed = this.tunnelsByConfigId.get(configId);
|
|
662
|
-
if (!managed)
|
|
673
|
+
if (!managed) {
|
|
674
|
+
throw new Error(`Tunnel "${configId}" not found`);
|
|
675
|
+
}
|
|
663
676
|
return managed.instance;
|
|
664
677
|
}
|
|
665
678
|
if (tunnelId) {
|
|
666
679
|
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
667
|
-
if (!managed)
|
|
680
|
+
if (!managed) {
|
|
681
|
+
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
682
|
+
}
|
|
668
683
|
return managed.instance;
|
|
669
684
|
}
|
|
670
685
|
throw new Error(`Either configId or tunnelId must be provided`);
|
|
@@ -843,12 +858,16 @@ var init_TunnelManager = __esm({
|
|
|
843
858
|
getManagedTunnel(configId, tunnelId) {
|
|
844
859
|
if (configId) {
|
|
845
860
|
const managed = this.tunnelsByConfigId.get(configId);
|
|
846
|
-
if (!managed)
|
|
861
|
+
if (!managed) {
|
|
862
|
+
throw new Error(`Tunnel "${configId}" not found`);
|
|
863
|
+
}
|
|
847
864
|
return managed;
|
|
848
865
|
}
|
|
849
866
|
if (tunnelId) {
|
|
850
867
|
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
851
|
-
if (!managed)
|
|
868
|
+
if (!managed) {
|
|
869
|
+
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
870
|
+
}
|
|
852
871
|
return managed;
|
|
853
872
|
}
|
|
854
873
|
throw new Error(`Either configId or tunnelId must be provided`);
|
|
@@ -1237,7 +1256,9 @@ var init_TunnelManager = __esm({
|
|
|
1237
1256
|
notifyPollingErrorListeners(tunnelId, errorMsg) {
|
|
1238
1257
|
try {
|
|
1239
1258
|
const listeners = this.tunnelPollingErrorListeners.get(tunnelId);
|
|
1240
|
-
if (!listeners)
|
|
1259
|
+
if (!listeners) {
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1241
1262
|
for (const [id, listener] of listeners) {
|
|
1242
1263
|
try {
|
|
1243
1264
|
listener(tunnelId, errorMsg);
|
|
@@ -1252,7 +1273,9 @@ var init_TunnelManager = __esm({
|
|
|
1252
1273
|
notifyErrorListeners(tunnelId, errorMsg, isFatal) {
|
|
1253
1274
|
try {
|
|
1254
1275
|
const listeners = this.tunnelErrorListeners.get(tunnelId);
|
|
1255
|
-
if (!listeners)
|
|
1276
|
+
if (!listeners) {
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1256
1279
|
for (const [id, listener] of listeners) {
|
|
1257
1280
|
try {
|
|
1258
1281
|
listener(tunnelId, errorMsg, isFatal);
|
|
@@ -1301,7 +1324,9 @@ var init_TunnelManager = __esm({
|
|
|
1301
1324
|
managedTunnel.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1302
1325
|
}
|
|
1303
1326
|
const listeners = this.tunnelDisconnectListeners.get(tunnelId);
|
|
1304
|
-
if (!listeners)
|
|
1327
|
+
if (!listeners) {
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1305
1330
|
for (const [id, listener] of listeners) {
|
|
1306
1331
|
try {
|
|
1307
1332
|
listener(tunnelId, error, messages);
|
|
@@ -1329,7 +1354,9 @@ var init_TunnelManager = __esm({
|
|
|
1329
1354
|
try {
|
|
1330
1355
|
logger.info("Tunnel will reconnect", { tunnelId, error, messages });
|
|
1331
1356
|
const listeners = this.tunnelWillReconnectListeners.get(tunnelId);
|
|
1332
|
-
if (!listeners)
|
|
1357
|
+
if (!listeners) {
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1333
1360
|
for (const [id, listener] of listeners) {
|
|
1334
1361
|
try {
|
|
1335
1362
|
listener(tunnelId, error, messages);
|
|
@@ -1357,7 +1384,9 @@ var init_TunnelManager = __esm({
|
|
|
1357
1384
|
try {
|
|
1358
1385
|
logger.info("Tunnel reconnecting", { tunnelId, retryCnt });
|
|
1359
1386
|
const listeners = this.tunnelReconnectingListeners.get(tunnelId);
|
|
1360
|
-
if (!listeners)
|
|
1387
|
+
if (!listeners) {
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1361
1390
|
for (const [id, listener] of listeners) {
|
|
1362
1391
|
try {
|
|
1363
1392
|
listener(tunnelId, retryCnt);
|
|
@@ -1435,7 +1464,9 @@ var init_TunnelManager = __esm({
|
|
|
1435
1464
|
managedTunnel.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1436
1465
|
}
|
|
1437
1466
|
const listeners = this.tunnelReconnectionFailedListeners.get(tunnelId);
|
|
1438
|
-
if (!listeners)
|
|
1467
|
+
if (!listeners) {
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1439
1470
|
for (const [id, listener] of listeners) {
|
|
1440
1471
|
try {
|
|
1441
1472
|
listener(tunnelId, retryCnt);
|
|
@@ -1459,7 +1490,9 @@ var init_TunnelManager = __esm({
|
|
|
1459
1490
|
try {
|
|
1460
1491
|
logger.debug("Error in Tunnel Worker", { tunnelId, errorMessage: error.message });
|
|
1461
1492
|
const listeners = this.tunnelWorkerErrorListeners.get(tunnelId);
|
|
1462
|
-
if (!listeners)
|
|
1493
|
+
if (!listeners) {
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1463
1496
|
for (const [id, listener] of listeners) {
|
|
1464
1497
|
try {
|
|
1465
1498
|
listener(tunnelId, error);
|
|
@@ -1945,6 +1978,7 @@ var init_handler = __esm({
|
|
|
1945
1978
|
"use strict";
|
|
1946
1979
|
init_cjs_shims();
|
|
1947
1980
|
init_types();
|
|
1981
|
+
init_logger();
|
|
1948
1982
|
init_TunnelManager();
|
|
1949
1983
|
init_remote_schema();
|
|
1950
1984
|
TunnelOperations = class {
|
|
@@ -1967,6 +2001,26 @@ var init_handler = __esm({
|
|
|
1967
2001
|
}
|
|
1968
2002
|
return status;
|
|
1969
2003
|
}
|
|
2004
|
+
// --- Placeholder response ---
|
|
2005
|
+
buildPendingTunnelResponse(tunnelid, tunnelConfig, configid, tunnelName, serve) {
|
|
2006
|
+
return {
|
|
2007
|
+
tunnelid,
|
|
2008
|
+
remoteurls: [],
|
|
2009
|
+
tunnelconfig: pinggyOptionsToTunnelConfig(tunnelConfig, configid, tunnelName, false, void 0, serve),
|
|
2010
|
+
status: this.buildStatus(tunnelid, "starting" /* Starting */, "" /* NoError */),
|
|
2011
|
+
stats: newStats()
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
2014
|
+
buildPendingTunnelResponseV2(tunnelid, tunnelConfig, configFromCli, configid, tunnelName, serve) {
|
|
2015
|
+
return {
|
|
2016
|
+
tunnelid,
|
|
2017
|
+
remoteurls: [],
|
|
2018
|
+
tunnelconfig: pinggyOptionsToTunnelConfigV1(tunnelConfig, configFromCli),
|
|
2019
|
+
status: this.buildStatus(tunnelid, "starting" /* Starting */, "" /* NoError */),
|
|
2020
|
+
stats: newStats(),
|
|
2021
|
+
greetmsg: ""
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
1970
2024
|
// --- Helper to construct TunnelResponse ---
|
|
1971
2025
|
async buildTunnelResponse(tunnelid, tunnelConfig, configid, tunnelName, serve) {
|
|
1972
2026
|
const [status, stats, tlsInfo, greetMsg, remoteurls] = await Promise.all([
|
|
@@ -2007,10 +2061,10 @@ var init_handler = __esm({
|
|
|
2007
2061
|
});
|
|
2008
2062
|
}
|
|
2009
2063
|
// --- Operations ---
|
|
2010
|
-
async handleStart(config) {
|
|
2064
|
+
async handleStart(config, noWait = false) {
|
|
2011
2065
|
try {
|
|
2012
2066
|
const opts = tunnelConfigToPinggyOptions(config);
|
|
2013
|
-
const
|
|
2067
|
+
const managed = await this.tunnelManager.createTunnel({
|
|
2014
2068
|
...opts,
|
|
2015
2069
|
configId: config.configid,
|
|
2016
2070
|
name: config.configname,
|
|
@@ -2018,36 +2072,52 @@ var init_handler = __esm({
|
|
|
2018
2072
|
serve: config.serve
|
|
2019
2073
|
}
|
|
2020
2074
|
});
|
|
2021
|
-
|
|
2075
|
+
const { tunnelid, tunnelName, serve, tunnelConfig } = managed;
|
|
2076
|
+
const startPromise = this.tunnelManager.startTunnel(tunnelid);
|
|
2077
|
+
if (noWait) {
|
|
2078
|
+
startPromise.catch((err) => {
|
|
2079
|
+
logger.error("No-wait startTunnel failed", { tunnelid, err: String(err) });
|
|
2080
|
+
});
|
|
2081
|
+
return this.buildPendingTunnelResponse(tunnelid, tunnelConfig, config.configid, tunnelName, serve);
|
|
2082
|
+
}
|
|
2083
|
+
await startPromise;
|
|
2022
2084
|
const tunnelPconfig = await this.tunnelManager.getTunnelConfig("", tunnelid);
|
|
2023
|
-
|
|
2024
|
-
return resp;
|
|
2085
|
+
return this.buildTunnelResponse(tunnelid, tunnelPconfig, config.configid, tunnelName, serve);
|
|
2025
2086
|
} catch (err) {
|
|
2026
2087
|
return this.error(ErrorCode.ErrorStartingTunnel, err, "Unknown error occurred while starting tunnel");
|
|
2027
2088
|
}
|
|
2028
2089
|
}
|
|
2029
|
-
async handleStartV2(config) {
|
|
2090
|
+
async handleStartV2(config, noWait = false) {
|
|
2030
2091
|
try {
|
|
2031
|
-
const
|
|
2092
|
+
const managed = await this.tunnelManager.createTunnel(config);
|
|
2093
|
+
const { tunnelid, serve, tunnelConfig } = managed;
|
|
2032
2094
|
await this.tunnelManager.startTunnel(tunnelid);
|
|
2033
2095
|
const tunnelPconfig = await this.tunnelManager.getTunnelConfig("", tunnelid);
|
|
2034
|
-
|
|
2035
|
-
return resp;
|
|
2096
|
+
return this.buildTunnelResponseV2(tunnelid, tunnelPconfig, config, config.configId, config.name, config.serve);
|
|
2036
2097
|
} catch (err) {
|
|
2037
2098
|
return this.error(ErrorCode.ErrorStartingTunnel, err, "Unknown error occurred while starting tunnel");
|
|
2038
2099
|
}
|
|
2039
2100
|
}
|
|
2040
|
-
async handleUpdateConfig(config) {
|
|
2101
|
+
async handleUpdateConfig(config, noWait = false) {
|
|
2041
2102
|
try {
|
|
2042
2103
|
const opts = tunnelConfigToPinggyOptions(config);
|
|
2043
|
-
const
|
|
2104
|
+
const updateOpts = {
|
|
2044
2105
|
...opts,
|
|
2045
2106
|
configId: config.configid,
|
|
2046
2107
|
name: config.configname,
|
|
2047
2108
|
optional: {
|
|
2048
2109
|
serve: config.serve
|
|
2049
2110
|
}
|
|
2050
|
-
}
|
|
2111
|
+
};
|
|
2112
|
+
if (noWait) {
|
|
2113
|
+
const existing = this.tunnelManager.getManagedTunnel(config.configid);
|
|
2114
|
+
if (!existing.tunnelConfig) throw new Error("Invalid tunnel state before configuration update");
|
|
2115
|
+
this.tunnelManager.updateConfig(updateOpts).catch((err) => {
|
|
2116
|
+
logger.error("No-wait updateConfig failed", { configid: config.configid, err: String(err) });
|
|
2117
|
+
});
|
|
2118
|
+
return this.buildPendingTunnelResponse(existing.tunnelid, existing.tunnelConfig, config.configid, existing.tunnelName, existing.serve);
|
|
2119
|
+
}
|
|
2120
|
+
const tunnel = await this.tunnelManager.updateConfig(updateOpts);
|
|
2051
2121
|
if (!tunnel.instance || !tunnel.tunnelConfig)
|
|
2052
2122
|
throw new Error("Invalid tunnel state after configuration update");
|
|
2053
2123
|
return this.buildTunnelResponse(tunnel.tunnelid, tunnel.tunnelConfig, config.configid, tunnel.tunnelName, tunnel.serve);
|
|
@@ -2055,8 +2125,17 @@ var init_handler = __esm({
|
|
|
2055
2125
|
return this.error(ErrorCode.InternalServerError, err, "Failed to update tunnel configuration");
|
|
2056
2126
|
}
|
|
2057
2127
|
}
|
|
2058
|
-
async handleUpdateConfigV2(config) {
|
|
2128
|
+
async handleUpdateConfigV2(config, noWait = false) {
|
|
2059
2129
|
try {
|
|
2130
|
+
if (noWait) {
|
|
2131
|
+
const existing = this.tunnelManager.getManagedTunnel(config.configId);
|
|
2132
|
+
console.log(existing);
|
|
2133
|
+
if (!existing.tunnelConfig) throw new Error("Invalid tunnel state before configuration update");
|
|
2134
|
+
this.tunnelManager.updateConfig(config).catch((err) => {
|
|
2135
|
+
logger.error("No-wait updateConfigV2 failed", { configId: config.configId, err: String(err) });
|
|
2136
|
+
});
|
|
2137
|
+
return this.buildPendingTunnelResponseV2(existing.tunnelid, existing.tunnelConfig, config, config.configId, existing.tunnelName, existing.serve);
|
|
2138
|
+
}
|
|
2060
2139
|
const tunnel = await this.tunnelManager.updateConfig(config);
|
|
2061
2140
|
if (!tunnel.instance || !tunnel.tunnelConfig)
|
|
2062
2141
|
throw new Error("Invalid tunnel state after configuration update");
|
|
@@ -2143,8 +2222,16 @@ var init_handler = __esm({
|
|
|
2143
2222
|
return this.error(ErrorCode.TunnelNotFound, err, "Failed to get tunnel information");
|
|
2144
2223
|
}
|
|
2145
2224
|
}
|
|
2146
|
-
async handleRestart(tunnelid) {
|
|
2225
|
+
async handleRestart(tunnelid, noWait = false) {
|
|
2147
2226
|
try {
|
|
2227
|
+
if (noWait) {
|
|
2228
|
+
const managed2 = this.tunnelManager.getManagedTunnel("", tunnelid);
|
|
2229
|
+
if (!managed2?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
|
|
2230
|
+
this.tunnelManager.restartTunnel(tunnelid).catch((err) => {
|
|
2231
|
+
logger.error("No-wait restartTunnel failed", { tunnelid, err: String(err) });
|
|
2232
|
+
});
|
|
2233
|
+
return this.buildPendingTunnelResponse(tunnelid, managed2.tunnelConfig, managed2.configId, managed2.tunnelName, managed2.serve);
|
|
2234
|
+
}
|
|
2148
2235
|
await this.tunnelManager.restartTunnel(tunnelid);
|
|
2149
2236
|
const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
|
|
2150
2237
|
if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
|
|
@@ -2463,7 +2550,7 @@ var init_websocket_handlers = __esm({
|
|
|
2463
2550
|
const dc = StartSchema.parse(raw);
|
|
2464
2551
|
queuedConfig = dc.tunnelConfig;
|
|
2465
2552
|
remoteManagementWebSocketPrinter.queueStart(dc.tunnelConfig);
|
|
2466
|
-
const result = await this.tunnelHandler.handleStart(dc.tunnelConfig);
|
|
2553
|
+
const result = await this.tunnelHandler.handleStart(dc.tunnelConfig, true);
|
|
2467
2554
|
remoteManagementWebSocketPrinter.handleStartResult(dc.tunnelConfig, result);
|
|
2468
2555
|
return this.wrapResponse(result, req);
|
|
2469
2556
|
} catch (e) {
|
|
@@ -2484,7 +2571,7 @@ var init_websocket_handlers = __esm({
|
|
|
2484
2571
|
const dc = StartV2Schema.parse(raw);
|
|
2485
2572
|
queuedConfig = dc.tunnelConfig;
|
|
2486
2573
|
remoteManagementWebSocketPrinter.queueStart(dc.tunnelConfig);
|
|
2487
|
-
const result = await this.tunnelHandler.handleStartV2(dc.tunnelConfig);
|
|
2574
|
+
const result = await this.tunnelHandler.handleStartV2(dc.tunnelConfig, true);
|
|
2488
2575
|
remoteManagementWebSocketPrinter.handleStartResult(dc.tunnelConfig, result);
|
|
2489
2576
|
return this.wrapResponse(result, req);
|
|
2490
2577
|
} catch (e) {
|
|
@@ -2533,7 +2620,7 @@ var init_websocket_handlers = __esm({
|
|
|
2533
2620
|
try {
|
|
2534
2621
|
const dc = RestartSchema.parse(raw);
|
|
2535
2622
|
remoteManagementWebSocketPrinter.printRestartRequested(dc.tunnelID);
|
|
2536
|
-
const result = await this.tunnelHandler.handleRestart(dc.tunnelID);
|
|
2623
|
+
const result = await this.tunnelHandler.handleRestart(dc.tunnelID, true);
|
|
2537
2624
|
remoteManagementWebSocketPrinter.handleRestartResult(dc.tunnelID, result);
|
|
2538
2625
|
return this.wrapResponse(result, req);
|
|
2539
2626
|
} catch (e) {
|
|
@@ -2548,7 +2635,7 @@ var init_websocket_handlers = __esm({
|
|
|
2548
2635
|
async handleUpdateConfigReq(req, raw) {
|
|
2549
2636
|
try {
|
|
2550
2637
|
const dc = UpdateConfigSchema.parse(raw);
|
|
2551
|
-
const result = await this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig);
|
|
2638
|
+
const result = await this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig, true);
|
|
2552
2639
|
return this.wrapResponse(result, req);
|
|
2553
2640
|
} catch (e) {
|
|
2554
2641
|
if (e instanceof import_zod2.default.ZodError) {
|
|
@@ -2562,7 +2649,7 @@ var init_websocket_handlers = __esm({
|
|
|
2562
2649
|
async handleUpdateConfigV2Req(req, raw) {
|
|
2563
2650
|
try {
|
|
2564
2651
|
const dc = UpdateConfigV2Schema.parse(raw);
|
|
2565
|
-
const result = await this.tunnelHandler.handleUpdateConfigV2(dc.tunnelConfig);
|
|
2652
|
+
const result = await this.tunnelHandler.handleUpdateConfigV2(dc.tunnelConfig, true);
|
|
2566
2653
|
return this.wrapResponse(result, req);
|
|
2567
2654
|
} catch (e) {
|
|
2568
2655
|
if (e instanceof import_zod2.default.ZodError) {
|
|
@@ -2758,9 +2845,14 @@ async function initiateRemoteManagement(remoteManagementConfig) {
|
|
|
2758
2845
|
try {
|
|
2759
2846
|
await handleWebSocketConnection(wsUrl, wsHost, remoteManagementConfig.apiKey);
|
|
2760
2847
|
} catch (error) {
|
|
2848
|
+
if (error instanceof RemoteManagementUnauthorizedError) {
|
|
2849
|
+
throw error;
|
|
2850
|
+
}
|
|
2761
2851
|
logger.warn("Remote management connection error", { error: String(error) });
|
|
2762
2852
|
}
|
|
2763
|
-
if (_stopRequested)
|
|
2853
|
+
if (_stopRequested) {
|
|
2854
|
+
break;
|
|
2855
|
+
}
|
|
2764
2856
|
printer_default.warn(`Remote management disconnected. Reconnecting in ${RECONNECT_SLEEP_MS / 1e3} seconds...`);
|
|
2765
2857
|
logger.info("Reconnecting to remote management after disconnect");
|
|
2766
2858
|
await sleep(RECONNECT_SLEEP_MS);
|
|
@@ -2769,22 +2861,34 @@ async function initiateRemoteManagement(remoteManagementConfig) {
|
|
|
2769
2861
|
logger.info("Remote management stopped.");
|
|
2770
2862
|
return getRemoteManagementState();
|
|
2771
2863
|
}
|
|
2772
|
-
async function handleWebSocketConnection(wsUrl, wsHost, token) {
|
|
2773
|
-
return new Promise((resolve) => {
|
|
2864
|
+
async function handleWebSocketConnection(wsUrl, wsHost, token, onOpenCallback) {
|
|
2865
|
+
return new Promise((resolve, reject) => {
|
|
2774
2866
|
const ws = new import_ws.default(wsUrl, {
|
|
2775
2867
|
headers: { Authorization: `Bearer ${token}` }
|
|
2776
2868
|
});
|
|
2777
2869
|
currentWs = ws;
|
|
2778
2870
|
let heartbeat = null;
|
|
2779
2871
|
let firstMessage = true;
|
|
2780
|
-
|
|
2781
|
-
|
|
2872
|
+
let settled = false;
|
|
2873
|
+
const cleanup = (err) => {
|
|
2874
|
+
if (settled) {
|
|
2875
|
+
return;
|
|
2876
|
+
}
|
|
2877
|
+
settled = true;
|
|
2878
|
+
if (heartbeat) {
|
|
2879
|
+
clearInterval(heartbeat);
|
|
2880
|
+
}
|
|
2782
2881
|
currentWs = null;
|
|
2783
|
-
|
|
2882
|
+
if (err) {
|
|
2883
|
+
reject(err);
|
|
2884
|
+
} else {
|
|
2885
|
+
resolve();
|
|
2886
|
+
}
|
|
2784
2887
|
};
|
|
2785
2888
|
ws.once("open", () => {
|
|
2786
2889
|
printer_default.success(`Connected to ${wsHost}`);
|
|
2787
2890
|
setRemoteManagementState({ status: RemoteManagementStatus.Running, errorMessage: "" });
|
|
2891
|
+
onOpenCallback?.();
|
|
2788
2892
|
heartbeat = setInterval(() => {
|
|
2789
2893
|
if (ws.readyState === import_ws.default.OPEN) ws.ping();
|
|
2790
2894
|
}, PING_INTERVAL_MS);
|
|
@@ -2812,13 +2916,14 @@ async function handleWebSocketConnection(wsUrl, wsHost, token) {
|
|
|
2812
2916
|
ws.on("unexpected-response", (_, res) => {
|
|
2813
2917
|
if (res.statusCode === 401) {
|
|
2814
2918
|
setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: `HTTP ${res.statusCode}` });
|
|
2815
|
-
printer_default.error("Unauthorized. Please enter a valid token.");
|
|
2816
2919
|
logger.error("Unauthorized (401) on remote management connect");
|
|
2920
|
+
cleanup(new RemoteManagementUnauthorizedError());
|
|
2817
2921
|
ws.close();
|
|
2818
2922
|
} else {
|
|
2819
2923
|
logger.warn("Unexpected HTTP response ", { statusCode: res.statusCode });
|
|
2820
2924
|
printer_default.warn(`Unexpected HTTP ${res.statusCode}. Retrying...`);
|
|
2821
2925
|
cleanup();
|
|
2926
|
+
ws.close();
|
|
2822
2927
|
}
|
|
2823
2928
|
});
|
|
2824
2929
|
ws.on("close", (code, reason) => {
|
|
@@ -2830,7 +2935,7 @@ async function handleWebSocketConnection(wsUrl, wsHost, token) {
|
|
|
2830
2935
|
ws.on("error", (err) => {
|
|
2831
2936
|
setRemoteManagementState({ status: RemoteManagementStatus.Error, errorMessage: err.message });
|
|
2832
2937
|
logger.warn("WebSocket error", { error: err.message });
|
|
2833
|
-
printer_default.
|
|
2938
|
+
printer_default.warn(err.message);
|
|
2834
2939
|
cleanup();
|
|
2835
2940
|
});
|
|
2836
2941
|
});
|
|
@@ -2860,6 +2965,58 @@ async function closeRemoteManagement(timeoutMs = 1e4) {
|
|
|
2860
2965
|
return getRemoteManagementState();
|
|
2861
2966
|
}
|
|
2862
2967
|
}
|
|
2968
|
+
function startRemoteManagement(remoteManagementConfig) {
|
|
2969
|
+
if (!remoteManagementConfig.apiKey || remoteManagementConfig.apiKey.trim().length === 0) {
|
|
2970
|
+
return Promise.reject(new Error("Remote management token is required"));
|
|
2971
|
+
}
|
|
2972
|
+
const wsUrl = remoteManagementConfig.serverUrl;
|
|
2973
|
+
const wsHost = extractHostname(wsUrl);
|
|
2974
|
+
logger.info("Remote management mode enabled.");
|
|
2975
|
+
_stopRequested = false;
|
|
2976
|
+
return new Promise((resolve, reject) => {
|
|
2977
|
+
let firstSettled = false;
|
|
2978
|
+
const settleOnce = (err) => {
|
|
2979
|
+
if (firstSettled) {
|
|
2980
|
+
return;
|
|
2981
|
+
}
|
|
2982
|
+
firstSettled = true;
|
|
2983
|
+
if (err) {
|
|
2984
|
+
reject(err);
|
|
2985
|
+
} else {
|
|
2986
|
+
resolve(getRemoteManagementState());
|
|
2987
|
+
}
|
|
2988
|
+
};
|
|
2989
|
+
const runLoop = async () => {
|
|
2990
|
+
const sigintHandler = () => {
|
|
2991
|
+
_stopRequested = true;
|
|
2992
|
+
};
|
|
2993
|
+
process.once("SIGINT", sigintHandler);
|
|
2994
|
+
while (!_stopRequested) {
|
|
2995
|
+
logger.info("Connecting to remote management", { wsUrl });
|
|
2996
|
+
setRemoteManagementState({ status: RemoteManagementStatus.Connecting, errorMessage: "" });
|
|
2997
|
+
try {
|
|
2998
|
+
await handleWebSocketConnection(wsUrl, wsHost, remoteManagementConfig.apiKey, () => settleOnce());
|
|
2999
|
+
} catch (error) {
|
|
3000
|
+
if (error instanceof RemoteManagementUnauthorizedError) {
|
|
3001
|
+
settleOnce(error);
|
|
3002
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
3003
|
+
return;
|
|
3004
|
+
}
|
|
3005
|
+
settleOnce();
|
|
3006
|
+
logger.warn("Remote management connection error", { error: String(error) });
|
|
3007
|
+
}
|
|
3008
|
+
if (_stopRequested) {
|
|
3009
|
+
break;
|
|
3010
|
+
}
|
|
3011
|
+
logger.info("Reconnecting to remote management after disconnect");
|
|
3012
|
+
await sleep(RECONNECT_SLEEP_MS);
|
|
3013
|
+
}
|
|
3014
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
3015
|
+
logger.info("Remote management stopped.");
|
|
3016
|
+
};
|
|
3017
|
+
runLoop().catch((err) => settleOnce(err instanceof Error ? err : new Error(String(err))));
|
|
3018
|
+
});
|
|
3019
|
+
}
|
|
2863
3020
|
function getRemoteManagementState() {
|
|
2864
3021
|
return _remoteManagementState;
|
|
2865
3022
|
}
|
|
@@ -2869,7 +3026,7 @@ function setRemoteManagementState(state, errorMessage) {
|
|
|
2869
3026
|
errorMessage: errorMessage || ""
|
|
2870
3027
|
};
|
|
2871
3028
|
}
|
|
2872
|
-
var import_ws, RECONNECT_SLEEP_MS, PING_INTERVAL_MS, _remoteManagementState, _stopRequested, currentWs;
|
|
3029
|
+
var import_ws, RECONNECT_SLEEP_MS, PING_INTERVAL_MS, RemoteManagementUnauthorizedError, _remoteManagementState, _stopRequested, currentWs;
|
|
2873
3030
|
var init_remoteManagement = __esm({
|
|
2874
3031
|
"src/remote_management/remoteManagement.ts"() {
|
|
2875
3032
|
"use strict";
|
|
@@ -2881,6 +3038,12 @@ var init_remoteManagement = __esm({
|
|
|
2881
3038
|
init_types();
|
|
2882
3039
|
RECONNECT_SLEEP_MS = 5e3;
|
|
2883
3040
|
PING_INTERVAL_MS = 3e4;
|
|
3041
|
+
RemoteManagementUnauthorizedError = class extends Error {
|
|
3042
|
+
constructor() {
|
|
3043
|
+
super("Unauthorized. Please enter a valid token.");
|
|
3044
|
+
this.name = "RemoteManagementUnauthorizedError";
|
|
3045
|
+
}
|
|
3046
|
+
};
|
|
2884
3047
|
_remoteManagementState = {
|
|
2885
3048
|
status: "NOT_RUNNING",
|
|
2886
3049
|
errorMessage: ""
|
|
@@ -2922,9 +3085,13 @@ var init_options = __esm({
|
|
|
2922
3085
|
vv: { type: "boolean", description: "Enable detailed logging for the Node.js SDK and Libpinggy, including both info and debug level logs." },
|
|
2923
3086
|
vvv: { type: "boolean", description: "Enable all logs from Cli, SDK and internal components." },
|
|
2924
3087
|
autoreconnect: { type: "string", short: "a", description: "Automatically reconnect tunnel on failure (enabled by default). Use -a false to disable." },
|
|
2925
|
-
// Save and load config
|
|
3088
|
+
// Save and load config (legacy file-based)
|
|
2926
3089
|
saveconf: { type: "string", description: "Create the configuration file based on the options provided here" },
|
|
2927
3090
|
conf: { type: "string", description: "Use the configuration file as base. Other options will be used to override this file" },
|
|
3091
|
+
// Used by `pinggy config save` and `buildAndStartTunnel` save flow
|
|
3092
|
+
save: { type: "boolean", short: "s", description: "Save the tunnel config (use with config save or -l)", hidden: true },
|
|
3093
|
+
name: { type: "string", description: "Name for the tunnel config", hidden: true },
|
|
3094
|
+
auto: { type: "boolean", description: "Mark tunnel config for auto-start", hidden: true },
|
|
2928
3095
|
// File server
|
|
2929
3096
|
serve: { type: "string", description: "Start a webserver to serve files from the specified path. Eg --serve /path/to/files" },
|
|
2930
3097
|
// Remote Control
|
|
@@ -2974,7 +3141,21 @@ function printHelpMessage() {
|
|
|
2974
3141
|
console.log(" pinggy -R0:localhost:3000 # Basic HTTP tunnel");
|
|
2975
3142
|
console.log(" pinggy --type tcp -R0:localhost:22 # TCP tunnel for SSH");
|
|
2976
3143
|
console.log(" pinggy -R0:localhost:8080 -L4300:localhost:4300 # HTTP tunnel with debugger");
|
|
2977
|
-
console.log(" pinggy tcp@ap.example.com -R0:localhost:22 # TCP tunnel to region
|
|
3144
|
+
console.log(" pinggy tcp@ap.example.com -R0:localhost:22 # TCP tunnel to region");
|
|
3145
|
+
console.log("\nConfig Management:");
|
|
3146
|
+
console.log(" pinggy config list # List saved configs");
|
|
3147
|
+
console.log(" pinggy config show my-tunnel # Show config details");
|
|
3148
|
+
console.log(" pinggy config save my-tunnel -l 3000 token@pro.pinggy.io # Save config");
|
|
3149
|
+
console.log(" pinggy config save my-tunnel --auto -l 3000 # Save with auto-start");
|
|
3150
|
+
console.log(" pinggy config update my-tunnel -l 4000 # Update saved config");
|
|
3151
|
+
console.log(" pinggy config delete my-tunnel # Delete saved config");
|
|
3152
|
+
console.log(" pinggy config auto my-tunnel # Enable auto-start");
|
|
3153
|
+
console.log(" pinggy config noauto my-tunnel # Disable auto-start");
|
|
3154
|
+
console.log("\nStart Saved Tunnels:");
|
|
3155
|
+
console.log(" pinggy start my-tunnel # Start saved tunnel");
|
|
3156
|
+
console.log(" pinggy start my-tunnel -l 4000 # Start with runtime overrides");
|
|
3157
|
+
console.log(" pinggy start tunnela tunnelb # Start multiple tunnels");
|
|
3158
|
+
console.log(" pinggy start --all # Start all auto-start tunnels\n");
|
|
2978
3159
|
}
|
|
2979
3160
|
var init_help = __esm({
|
|
2980
3161
|
"src/cli/help.ts"() {
|
|
@@ -2984,6 +3165,74 @@ var init_help = __esm({
|
|
|
2984
3165
|
}
|
|
2985
3166
|
});
|
|
2986
3167
|
|
|
3168
|
+
// src/utils/parseArgs.ts
|
|
3169
|
+
function isAttachedReverseOrLocalFlag(arg) {
|
|
3170
|
+
return /^-[RL].+/.test(arg);
|
|
3171
|
+
}
|
|
3172
|
+
function shouldMergeReverseOrLocalFragment(current, next) {
|
|
3173
|
+
if (next.startsWith("-")) {
|
|
3174
|
+
return false;
|
|
3175
|
+
}
|
|
3176
|
+
if (next.startsWith(".")) {
|
|
3177
|
+
return true;
|
|
3178
|
+
}
|
|
3179
|
+
const body = current.slice(2);
|
|
3180
|
+
if (body.endsWith(":")) {
|
|
3181
|
+
return true;
|
|
3182
|
+
}
|
|
3183
|
+
if (body.includes("//") && !body.includes(":")) {
|
|
3184
|
+
return true;
|
|
3185
|
+
}
|
|
3186
|
+
return false;
|
|
3187
|
+
}
|
|
3188
|
+
function preprocessWindowsArgs(args) {
|
|
3189
|
+
if (os2.platform() !== "win32") {
|
|
3190
|
+
return args;
|
|
3191
|
+
}
|
|
3192
|
+
;
|
|
3193
|
+
const out = [];
|
|
3194
|
+
let i = 0;
|
|
3195
|
+
while (i < args.length) {
|
|
3196
|
+
const arg = args[i];
|
|
3197
|
+
if (isAttachedReverseOrLocalFlag(arg)) {
|
|
3198
|
+
let merged = arg;
|
|
3199
|
+
while (i + 1 < args.length && shouldMergeReverseOrLocalFragment(merged, args[i + 1])) {
|
|
3200
|
+
merged += args[i + 1];
|
|
3201
|
+
i++;
|
|
3202
|
+
}
|
|
3203
|
+
out.push(merged);
|
|
3204
|
+
i++;
|
|
3205
|
+
continue;
|
|
3206
|
+
}
|
|
3207
|
+
out.push(arg);
|
|
3208
|
+
i++;
|
|
3209
|
+
}
|
|
3210
|
+
return out;
|
|
3211
|
+
}
|
|
3212
|
+
function parseCliArgs(options, overrideArgs) {
|
|
3213
|
+
const rawArgs = overrideArgs ?? process.argv.slice(2);
|
|
3214
|
+
const processedArgs = preprocessWindowsArgs(rawArgs);
|
|
3215
|
+
const parsed = (0, import_util5.parseArgs)({
|
|
3216
|
+
args: processedArgs,
|
|
3217
|
+
options,
|
|
3218
|
+
allowPositionals: true
|
|
3219
|
+
});
|
|
3220
|
+
const hasAnyArgs = parsed.positionals.length > 0 || Object.values(parsed.values).some((v) => v !== void 0 && v !== false);
|
|
3221
|
+
return {
|
|
3222
|
+
...parsed,
|
|
3223
|
+
hasAnyArgs
|
|
3224
|
+
};
|
|
3225
|
+
}
|
|
3226
|
+
var import_util5, os2;
|
|
3227
|
+
var init_parseArgs = __esm({
|
|
3228
|
+
"src/utils/parseArgs.ts"() {
|
|
3229
|
+
"use strict";
|
|
3230
|
+
init_cjs_shims();
|
|
3231
|
+
import_util5 = require("util");
|
|
3232
|
+
os2 = __toESM(require("os"), 1);
|
|
3233
|
+
}
|
|
3234
|
+
});
|
|
3235
|
+
|
|
2987
3236
|
// src/cli/defaults.ts
|
|
2988
3237
|
var defaultOptions;
|
|
2989
3238
|
var init_defaults = __esm({
|
|
@@ -2991,6 +3240,7 @@ var init_defaults = __esm({
|
|
|
2991
3240
|
"use strict";
|
|
2992
3241
|
init_cjs_shims();
|
|
2993
3242
|
defaultOptions = {
|
|
3243
|
+
version: "1.0",
|
|
2994
3244
|
token: void 0,
|
|
2995
3245
|
// No default token
|
|
2996
3246
|
serverAddress: "a.pinggy.io",
|
|
@@ -3162,7 +3412,9 @@ function removeIPv6Brackets(ip) {
|
|
|
3162
3412
|
}
|
|
3163
3413
|
function isValidServerAddress(host) {
|
|
3164
3414
|
const normalized = removeIPv6Brackets(host.trim());
|
|
3165
|
-
if (!normalized)
|
|
3415
|
+
if (!normalized) {
|
|
3416
|
+
return false;
|
|
3417
|
+
}
|
|
3166
3418
|
return domainRegex.test(normalized) || (0, import_net2.isIP)(normalized) !== 0;
|
|
3167
3419
|
}
|
|
3168
3420
|
function isKeyword(str) {
|
|
@@ -3174,7 +3426,9 @@ function parseUserAndDomain(str) {
|
|
|
3174
3426
|
let server;
|
|
3175
3427
|
let qrCode;
|
|
3176
3428
|
let forceFlag;
|
|
3177
|
-
if (!str)
|
|
3429
|
+
if (!str) {
|
|
3430
|
+
return { token, type, server, qrCode, forceFlag };
|
|
3431
|
+
}
|
|
3178
3432
|
if (str.includes("@")) {
|
|
3179
3433
|
const [user, domain] = str.split("@", 2);
|
|
3180
3434
|
if (isValidServerAddress(domain)) {
|
|
@@ -3227,21 +3481,39 @@ function parseUsers(positionalArgs, explicitToken) {
|
|
|
3227
3481
|
let remaining = [...positionalArgs];
|
|
3228
3482
|
if (typeof explicitToken === "string") {
|
|
3229
3483
|
const parsed = parseUserAndDomain(explicitToken);
|
|
3230
|
-
if (parsed.server)
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
if (parsed.
|
|
3234
|
-
|
|
3484
|
+
if (parsed.server) {
|
|
3485
|
+
server = parsed.server;
|
|
3486
|
+
}
|
|
3487
|
+
if (parsed.type) {
|
|
3488
|
+
type = parsed.type;
|
|
3489
|
+
}
|
|
3490
|
+
if (parsed.token) {
|
|
3491
|
+
token = parsed.token;
|
|
3492
|
+
}
|
|
3493
|
+
if (parsed.forceFlag) {
|
|
3494
|
+
forceFlag = true;
|
|
3495
|
+
}
|
|
3496
|
+
if (parsed.qrCode) {
|
|
3497
|
+
qrCode = true;
|
|
3498
|
+
}
|
|
3235
3499
|
}
|
|
3236
3500
|
if (remaining.length > 0) {
|
|
3237
3501
|
const first = remaining[0];
|
|
3238
3502
|
const parsed = parseUserAndDomain(first);
|
|
3239
3503
|
if (parsed.server) {
|
|
3240
3504
|
server = parsed.server;
|
|
3241
|
-
if (parsed.type)
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
if (parsed.
|
|
3505
|
+
if (parsed.type) {
|
|
3506
|
+
type = parsed.type;
|
|
3507
|
+
}
|
|
3508
|
+
if (parsed.token) {
|
|
3509
|
+
token = parsed.token;
|
|
3510
|
+
}
|
|
3511
|
+
if (parsed.forceFlag) {
|
|
3512
|
+
forceFlag = true;
|
|
3513
|
+
}
|
|
3514
|
+
if (parsed.qrCode) {
|
|
3515
|
+
qrCode = true;
|
|
3516
|
+
}
|
|
3245
3517
|
remaining = remaining.slice(1);
|
|
3246
3518
|
}
|
|
3247
3519
|
}
|
|
@@ -3254,7 +3526,9 @@ function parseType(finalConfig, values, inferredType) {
|
|
|
3254
3526
|
}
|
|
3255
3527
|
}
|
|
3256
3528
|
function parseLocalPort(finalConfig, values) {
|
|
3257
|
-
if (typeof values.localport !== "string")
|
|
3529
|
+
if (typeof values.localport !== "string") {
|
|
3530
|
+
return null;
|
|
3531
|
+
}
|
|
3258
3532
|
let lp = values.localport.trim();
|
|
3259
3533
|
let isHttps = false;
|
|
3260
3534
|
if (lp.startsWith("https://")) {
|
|
@@ -3286,7 +3560,9 @@ function parseLocalPort(finalConfig, values) {
|
|
|
3286
3560
|
}
|
|
3287
3561
|
function isValidHostAddress(host) {
|
|
3288
3562
|
const normalized = removeIPv6Brackets(host.trim());
|
|
3289
|
-
if (normalized.length === 0)
|
|
3563
|
+
if (normalized.length === 0) {
|
|
3564
|
+
return false;
|
|
3565
|
+
}
|
|
3290
3566
|
return normalized === "localhost" || (0, import_net2.isIP)(normalized) !== 0;
|
|
3291
3567
|
}
|
|
3292
3568
|
function ipv6SafeSplitColon(s) {
|
|
@@ -3329,7 +3605,9 @@ function parseDefaultForwarding(forwarding) {
|
|
|
3329
3605
|
}
|
|
3330
3606
|
function parseAdditionalForwarding(forwarding) {
|
|
3331
3607
|
const toPort = (v) => {
|
|
3332
|
-
if (!v)
|
|
3608
|
+
if (!v) {
|
|
3609
|
+
return null;
|
|
3610
|
+
}
|
|
3333
3611
|
const n = parseInt(v, 10);
|
|
3334
3612
|
return Number.isNaN(n) ? null : n;
|
|
3335
3613
|
};
|
|
@@ -3408,7 +3686,9 @@ function parseReverseTunnelAddr(finalConfig, values, primaryType) {
|
|
|
3408
3686
|
});
|
|
3409
3687
|
} else if (slicedForwarding.length === 4) {
|
|
3410
3688
|
const parsed = parseAdditionalForwarding(forwarding);
|
|
3411
|
-
if (parsed instanceof Error)
|
|
3689
|
+
if (parsed instanceof Error) {
|
|
3690
|
+
return parsed;
|
|
3691
|
+
}
|
|
3412
3692
|
forwardingData.push(parsed);
|
|
3413
3693
|
} else {
|
|
3414
3694
|
return new Error(
|
|
@@ -3420,7 +3700,9 @@ function parseReverseTunnelAddr(finalConfig, values, primaryType) {
|
|
|
3420
3700
|
return null;
|
|
3421
3701
|
}
|
|
3422
3702
|
function parseLocalTunnelAddr(finalConfig, values) {
|
|
3423
|
-
if (!Array.isArray(values.L) || values.L.length === 0)
|
|
3703
|
+
if (!Array.isArray(values.L) || values.L.length === 0) {
|
|
3704
|
+
return null;
|
|
3705
|
+
}
|
|
3424
3706
|
const firstL = values.L[0];
|
|
3425
3707
|
const parts = ipv6SafeSplitColon(firstL);
|
|
3426
3708
|
let debuggerHost = "localhost";
|
|
@@ -3444,7 +3726,9 @@ function parseLocalTunnelAddr(finalConfig, values) {
|
|
|
3444
3726
|
}
|
|
3445
3727
|
function parseDebugger(finalConfig, values) {
|
|
3446
3728
|
let dbg = values.debugger;
|
|
3447
|
-
if (typeof dbg !== "string")
|
|
3729
|
+
if (typeof dbg !== "string") {
|
|
3730
|
+
return;
|
|
3731
|
+
}
|
|
3448
3732
|
dbg = dbg.startsWith(":") ? dbg.slice(1) : dbg;
|
|
3449
3733
|
const d = parseInt(dbg, 10);
|
|
3450
3734
|
if (!Number.isNaN(d) && isValidPort(d)) {
|
|
@@ -3459,7 +3743,7 @@ function parseToken(finalConfig, explicitToken) {
|
|
|
3459
3743
|
finalConfig.token = explicitToken;
|
|
3460
3744
|
}
|
|
3461
3745
|
}
|
|
3462
|
-
function
|
|
3746
|
+
function parseArgs2(finalConfig, remainingPositionals) {
|
|
3463
3747
|
let localserverTls = "";
|
|
3464
3748
|
localserverTls = parseExtendedOptions(remainingPositionals, finalConfig, localserverTls);
|
|
3465
3749
|
if (localserverTls.length > 0 && finalConfig.forwarding) {
|
|
@@ -3470,10 +3754,10 @@ function parseArgs(finalConfig, remainingPositionals) {
|
|
|
3470
3754
|
}
|
|
3471
3755
|
function storeJson(config, saveconf) {
|
|
3472
3756
|
if (saveconf) {
|
|
3473
|
-
const
|
|
3757
|
+
const path7 = saveconf;
|
|
3474
3758
|
try {
|
|
3475
|
-
import_fs4.default.writeFileSync(
|
|
3476
|
-
logger.info(`Configuration saved to ${
|
|
3759
|
+
import_fs4.default.writeFileSync(path7, JSON.stringify(config, null, 2), { encoding: "utf-8", flag: "w" });
|
|
3760
|
+
logger.info(`Configuration saved to ${path7}`);
|
|
3477
3761
|
} catch (err) {
|
|
3478
3762
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3479
3763
|
logger.error("Error loading configuration:", msg);
|
|
@@ -3503,7 +3787,9 @@ function isSaveConfOption(values) {
|
|
|
3503
3787
|
}
|
|
3504
3788
|
function parseServe(finalConfig, values) {
|
|
3505
3789
|
const sv = values.serve;
|
|
3506
|
-
if (typeof sv !== "string" || sv.trim().length === 0)
|
|
3790
|
+
if (typeof sv !== "string" || sv.trim().length === 0) {
|
|
3791
|
+
return null;
|
|
3792
|
+
}
|
|
3507
3793
|
finalConfig.optional.serve = sv;
|
|
3508
3794
|
return null;
|
|
3509
3795
|
}
|
|
@@ -3521,7 +3807,7 @@ function parseAutoReconnect(finalConfig, values) {
|
|
|
3521
3807
|
}
|
|
3522
3808
|
return null;
|
|
3523
3809
|
}
|
|
3524
|
-
async function buildFinalConfig(values, positionals) {
|
|
3810
|
+
async function buildFinalConfig(values, positionals, baseConfig) {
|
|
3525
3811
|
let token;
|
|
3526
3812
|
let server;
|
|
3527
3813
|
let type;
|
|
@@ -3529,7 +3815,7 @@ async function buildFinalConfig(values, positionals) {
|
|
|
3529
3815
|
let qrCode = false;
|
|
3530
3816
|
let finalConfig = new Object();
|
|
3531
3817
|
let saveconf = isSaveConfOption(values);
|
|
3532
|
-
const configFromFile = loadJsonConfig(values);
|
|
3818
|
+
const configFromFile = baseConfig || loadJsonConfig(values);
|
|
3533
3819
|
const userParse = parseUsers(positionals, values.token);
|
|
3534
3820
|
token = userParse.token;
|
|
3535
3821
|
server = userParse.server;
|
|
@@ -3555,19 +3841,33 @@ async function buildFinalConfig(values, positionals) {
|
|
|
3555
3841
|
type = parseType(finalConfig, values, type);
|
|
3556
3842
|
parseToken(finalConfig, token || values.token);
|
|
3557
3843
|
const dbgErr = parseDebugger(finalConfig, values);
|
|
3558
|
-
if (dbgErr instanceof Error)
|
|
3844
|
+
if (dbgErr instanceof Error) {
|
|
3845
|
+
throw dbgErr;
|
|
3846
|
+
}
|
|
3559
3847
|
const lpErr = parseLocalPort(finalConfig, values);
|
|
3560
|
-
if (lpErr instanceof Error)
|
|
3848
|
+
if (lpErr instanceof Error) {
|
|
3849
|
+
throw lpErr;
|
|
3850
|
+
}
|
|
3561
3851
|
const rErr = parseReverseTunnelAddr(finalConfig, values, type);
|
|
3562
|
-
if (rErr instanceof Error)
|
|
3852
|
+
if (rErr instanceof Error) {
|
|
3853
|
+
throw rErr;
|
|
3854
|
+
}
|
|
3563
3855
|
const lErr = parseLocalTunnelAddr(finalConfig, values);
|
|
3564
|
-
if (lErr instanceof Error)
|
|
3856
|
+
if (lErr instanceof Error) {
|
|
3857
|
+
throw lErr;
|
|
3858
|
+
}
|
|
3565
3859
|
const serveErr = parseServe(finalConfig, values);
|
|
3566
|
-
if (serveErr instanceof Error)
|
|
3860
|
+
if (serveErr instanceof Error) {
|
|
3861
|
+
throw serveErr;
|
|
3862
|
+
}
|
|
3567
3863
|
const autoReconnectErr = parseAutoReconnect(finalConfig, values);
|
|
3568
|
-
if (autoReconnectErr instanceof Error)
|
|
3569
|
-
|
|
3570
|
-
|
|
3864
|
+
if (autoReconnectErr instanceof Error) {
|
|
3865
|
+
throw autoReconnectErr;
|
|
3866
|
+
}
|
|
3867
|
+
if (forceFlag || values.force) {
|
|
3868
|
+
finalConfig.force = true;
|
|
3869
|
+
}
|
|
3870
|
+
parseArgs2(finalConfig, remainingPositionals);
|
|
3571
3871
|
storeJson(finalConfig, saveconf);
|
|
3572
3872
|
return finalConfig;
|
|
3573
3873
|
}
|
|
@@ -3598,63 +3898,6 @@ var init_buildConfig = __esm({
|
|
|
3598
3898
|
}
|
|
3599
3899
|
});
|
|
3600
3900
|
|
|
3601
|
-
// src/utils/parseArgs.ts
|
|
3602
|
-
function isAttachedReverseOrLocalFlag(arg) {
|
|
3603
|
-
return /^-[RL].+/.test(arg);
|
|
3604
|
-
}
|
|
3605
|
-
function shouldMergeReverseOrLocalFragment(current, next) {
|
|
3606
|
-
if (next.startsWith("-")) return false;
|
|
3607
|
-
if (next.startsWith(".")) return true;
|
|
3608
|
-
const body = current.slice(2);
|
|
3609
|
-
if (body.endsWith(":")) return true;
|
|
3610
|
-
if (body.includes("//") && !body.includes(":")) return true;
|
|
3611
|
-
return false;
|
|
3612
|
-
}
|
|
3613
|
-
function preprocessWindowsArgs(args) {
|
|
3614
|
-
if (os2.platform() !== "win32") return args;
|
|
3615
|
-
const out = [];
|
|
3616
|
-
let i = 0;
|
|
3617
|
-
while (i < args.length) {
|
|
3618
|
-
const arg = args[i];
|
|
3619
|
-
if (isAttachedReverseOrLocalFlag(arg)) {
|
|
3620
|
-
let merged = arg;
|
|
3621
|
-
while (i + 1 < args.length && shouldMergeReverseOrLocalFragment(merged, args[i + 1])) {
|
|
3622
|
-
merged += args[i + 1];
|
|
3623
|
-
i++;
|
|
3624
|
-
}
|
|
3625
|
-
out.push(merged);
|
|
3626
|
-
i++;
|
|
3627
|
-
continue;
|
|
3628
|
-
}
|
|
3629
|
-
out.push(arg);
|
|
3630
|
-
i++;
|
|
3631
|
-
}
|
|
3632
|
-
return out;
|
|
3633
|
-
}
|
|
3634
|
-
function parseCliArgs(options) {
|
|
3635
|
-
const rawArgs = process.argv.slice(2);
|
|
3636
|
-
const processedArgs = preprocessWindowsArgs(rawArgs);
|
|
3637
|
-
const parsed = (0, import_util6.parseArgs)({
|
|
3638
|
-
args: processedArgs,
|
|
3639
|
-
options,
|
|
3640
|
-
allowPositionals: true
|
|
3641
|
-
});
|
|
3642
|
-
const hasAnyArgs = parsed.positionals.length > 0 || Object.values(parsed.values).some((v) => v !== void 0 && v !== false);
|
|
3643
|
-
return {
|
|
3644
|
-
...parsed,
|
|
3645
|
-
hasAnyArgs
|
|
3646
|
-
};
|
|
3647
|
-
}
|
|
3648
|
-
var import_util6, os2;
|
|
3649
|
-
var init_parseArgs = __esm({
|
|
3650
|
-
"src/utils/parseArgs.ts"() {
|
|
3651
|
-
"use strict";
|
|
3652
|
-
init_cjs_shims();
|
|
3653
|
-
import_util6 = require("util");
|
|
3654
|
-
os2 = __toESM(require("os"), 1);
|
|
3655
|
-
}
|
|
3656
|
-
});
|
|
3657
|
-
|
|
3658
3901
|
// src/utils/getFreePort.ts
|
|
3659
3902
|
function getFreePort(webDebugger) {
|
|
3660
3903
|
return new Promise((resolve, reject) => {
|
|
@@ -3698,13 +3941,12 @@ var init_getFreePort = __esm({
|
|
|
3698
3941
|
async function createQrCodes(urls) {
|
|
3699
3942
|
const codes = [];
|
|
3700
3943
|
for (const url of urls) {
|
|
3701
|
-
const
|
|
3702
|
-
type: "
|
|
3703
|
-
|
|
3704
|
-
margin: 0,
|
|
3944
|
+
const raw = await import_qrcode.default.toString(url, {
|
|
3945
|
+
type: "utf8",
|
|
3946
|
+
margin: 2,
|
|
3705
3947
|
errorCorrectionLevel: "L"
|
|
3706
3948
|
});
|
|
3707
|
-
codes.push(
|
|
3949
|
+
codes.push(raw);
|
|
3708
3950
|
}
|
|
3709
3951
|
return codes;
|
|
3710
3952
|
}
|
|
@@ -3722,6 +3964,7 @@ function getTuiConfig() {
|
|
|
3722
3964
|
return {
|
|
3723
3965
|
maxRequestPairs: defaultTuiConfig.maxRequestPairs,
|
|
3724
3966
|
visibleRequestCount: defaultTuiConfig.visibleRequestCount,
|
|
3967
|
+
visibleUrlCount: defaultTuiConfig.visibleUrlCount,
|
|
3725
3968
|
viewportScrollMargin: defaultTuiConfig.viewportScrollMargin,
|
|
3726
3969
|
inactivityHttpSelectorTimeoutMs: defaultTuiConfig.inactivityHttpSelectorTimeoutMs
|
|
3727
3970
|
};
|
|
@@ -3734,6 +3977,7 @@ var init_config = __esm({
|
|
|
3734
3977
|
defaultTuiConfig = {
|
|
3735
3978
|
maxRequestPairs: 100,
|
|
3736
3979
|
visibleRequestCount: 10,
|
|
3980
|
+
visibleUrlCount: 7,
|
|
3737
3981
|
viewportScrollMargin: 2,
|
|
3738
3982
|
inactivityHttpSelectorTimeoutMs: 1e4
|
|
3739
3983
|
};
|
|
@@ -3974,7 +4218,7 @@ function createFullUI(screen, urls, greet, tunnelConfig) {
|
|
|
3974
4218
|
width: "100%-2",
|
|
3975
4219
|
height: `100%-${lowerSectionTop + 6}`
|
|
3976
4220
|
});
|
|
3977
|
-
const isQrCodeRequested = tunnelConfig?.
|
|
4221
|
+
const isQrCodeRequested = tunnelConfig?.isQRCode || false;
|
|
3978
4222
|
const requestsBox = import_blessed.default.box({
|
|
3979
4223
|
parent: lowerSection,
|
|
3980
4224
|
top: 0,
|
|
@@ -4134,8 +4378,24 @@ var init_utils = __esm({
|
|
|
4134
4378
|
// src/tui/blessed/components/DisplayUpdaters.ts
|
|
4135
4379
|
function updateUrlsDisplay(urlsBox, screen, urls, currentQrIndex) {
|
|
4136
4380
|
if (!urlsBox) return;
|
|
4137
|
-
|
|
4138
|
-
|
|
4381
|
+
const config = getTuiConfig();
|
|
4382
|
+
const { visibleUrlCount } = config;
|
|
4383
|
+
let viewportStart = 0;
|
|
4384
|
+
if (urls.length > visibleUrlCount) {
|
|
4385
|
+
viewportStart = Math.max(0, Math.min(
|
|
4386
|
+
currentQrIndex - Math.floor(visibleUrlCount / 2),
|
|
4387
|
+
urls.length - visibleUrlCount
|
|
4388
|
+
));
|
|
4389
|
+
}
|
|
4390
|
+
const viewportEnd = Math.min(viewportStart + visibleUrlCount, urls.length);
|
|
4391
|
+
const visibleUrls = urls.slice(viewportStart, viewportEnd);
|
|
4392
|
+
let content = "{green-fg}{bold}Public URLs{/bold}{/green-fg}";
|
|
4393
|
+
if (viewportStart > 0) {
|
|
4394
|
+
content += ` {gray-fg}\u2191 ${viewportStart} more{/gray-fg}`;
|
|
4395
|
+
}
|
|
4396
|
+
content += "\n";
|
|
4397
|
+
visibleUrls.forEach((url, i) => {
|
|
4398
|
+
const index = viewportStart + i;
|
|
4139
4399
|
const isSelected = index === currentQrIndex;
|
|
4140
4400
|
const prefix = isSelected ? "\u2192 " : "\u2022 ";
|
|
4141
4401
|
const color = isSelected ? "yellow" : "magenta";
|
|
@@ -4147,6 +4407,11 @@ function updateUrlsDisplay(urlsBox, screen, urls, currentQrIndex) {
|
|
|
4147
4407
|
`;
|
|
4148
4408
|
}
|
|
4149
4409
|
});
|
|
4410
|
+
const itemsBelow = urls.length - viewportEnd;
|
|
4411
|
+
if (itemsBelow > 0) {
|
|
4412
|
+
content += `{gray-fg}\u2193 ${itemsBelow} more{/gray-fg}
|
|
4413
|
+
`;
|
|
4414
|
+
}
|
|
4150
4415
|
urlsBox.setContent(content);
|
|
4151
4416
|
screen.render();
|
|
4152
4417
|
}
|
|
@@ -4859,7 +5124,7 @@ var init_TunnelTui = __esm({
|
|
|
4859
5124
|
}
|
|
4860
5125
|
}
|
|
4861
5126
|
async generateQrCodes() {
|
|
4862
|
-
if (this.tunnelConfig?.
|
|
5127
|
+
if (this.tunnelConfig?.isQRCode && this.urls.length > 0) {
|
|
4863
5128
|
this.qrCodes = await createQrCodes(this.urls);
|
|
4864
5129
|
this.updateQrCodeDisplay();
|
|
4865
5130
|
}
|
|
@@ -5096,7 +5361,7 @@ async function startCli(finalConfig, manager) {
|
|
|
5096
5361
|
});
|
|
5097
5362
|
}
|
|
5098
5363
|
manager2.registerWorkerErrorListner(tunnel.tunnelid, (_tunnelid, error) => {
|
|
5099
|
-
printer_default.
|
|
5364
|
+
printer_default.fatal(`${error.message}`);
|
|
5100
5365
|
});
|
|
5101
5366
|
await manager2.startTunnel(tunnel.tunnelid);
|
|
5102
5367
|
printer_default.stopSpinnerSuccess(" Connected to Pinggy");
|
|
@@ -5221,7 +5486,7 @@ async function startCli(finalConfig, manager) {
|
|
|
5221
5486
|
}
|
|
5222
5487
|
} catch (err) {
|
|
5223
5488
|
printer_default.stopSpinnerFail("Failed to connect");
|
|
5224
|
-
printer_default.
|
|
5489
|
+
printer_default.fatal(err.message || "Unknown error");
|
|
5225
5490
|
throw err;
|
|
5226
5491
|
}
|
|
5227
5492
|
}
|
|
@@ -5246,9 +5511,604 @@ var init_starCli = __esm({
|
|
|
5246
5511
|
}
|
|
5247
5512
|
});
|
|
5248
5513
|
|
|
5514
|
+
// src/utils/configDir.ts
|
|
5515
|
+
function getPinggyConfigDir() {
|
|
5516
|
+
const platform2 = import_os2.default.platform();
|
|
5517
|
+
let baseDir;
|
|
5518
|
+
if (platform2 === "win32") {
|
|
5519
|
+
baseDir = process.env.APPDATA || import_path5.default.join(import_os2.default.homedir(), "AppData", "Roaming");
|
|
5520
|
+
} else {
|
|
5521
|
+
baseDir = process.env.XDG_CONFIG_HOME || import_path5.default.join(import_os2.default.homedir(), ".config");
|
|
5522
|
+
}
|
|
5523
|
+
return import_path5.default.join(baseDir, "pinggy");
|
|
5524
|
+
}
|
|
5525
|
+
function getTunnelConfigDir() {
|
|
5526
|
+
return import_path5.default.join(getPinggyConfigDir(), "tunnels");
|
|
5527
|
+
}
|
|
5528
|
+
function ensureTunnelConfigDir() {
|
|
5529
|
+
const dir = getTunnelConfigDir();
|
|
5530
|
+
import_fs5.default.mkdirSync(dir, { recursive: true });
|
|
5531
|
+
return dir;
|
|
5532
|
+
}
|
|
5533
|
+
var import_os2, import_path5, import_fs5;
|
|
5534
|
+
var init_configDir = __esm({
|
|
5535
|
+
"src/utils/configDir.ts"() {
|
|
5536
|
+
"use strict";
|
|
5537
|
+
init_cjs_shims();
|
|
5538
|
+
import_os2 = __toESM(require("os"), 1);
|
|
5539
|
+
import_path5 = __toESM(require("path"), 1);
|
|
5540
|
+
import_fs5 = __toESM(require("fs"), 1);
|
|
5541
|
+
}
|
|
5542
|
+
});
|
|
5543
|
+
|
|
5544
|
+
// src/cli/configStore.ts
|
|
5545
|
+
function buildFilename(name, configId) {
|
|
5546
|
+
return `${name}_${configId}.json`;
|
|
5547
|
+
}
|
|
5548
|
+
function sanitizeName(name) {
|
|
5549
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
5550
|
+
}
|
|
5551
|
+
function validateName(name) {
|
|
5552
|
+
if (!name || name.trim().length === 0) {
|
|
5553
|
+
return new Error("Tunnel name cannot be empty.");
|
|
5554
|
+
}
|
|
5555
|
+
if (name.length > 128) {
|
|
5556
|
+
return new Error("Tunnel name cannot exceed 128 characters.");
|
|
5557
|
+
}
|
|
5558
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
5559
|
+
return new Error("Tunnel name can only contain alphanumeric characters, hyphens, and underscores.");
|
|
5560
|
+
}
|
|
5561
|
+
return null;
|
|
5562
|
+
}
|
|
5563
|
+
function readConfigFile(filePath) {
|
|
5564
|
+
try {
|
|
5565
|
+
const data = import_fs6.default.readFileSync(filePath, { encoding: "utf-8" });
|
|
5566
|
+
return JSON.parse(data);
|
|
5567
|
+
} catch (err) {
|
|
5568
|
+
logger.warn(`Failed to read config file ${filePath}:`, err);
|
|
5569
|
+
return null;
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
5572
|
+
function writeConfigFile(filePath, config) {
|
|
5573
|
+
import_fs6.default.writeFileSync(filePath, JSON.stringify(config, null, 2), { encoding: "utf-8" });
|
|
5574
|
+
}
|
|
5575
|
+
function listSavedConfigs() {
|
|
5576
|
+
const dir = getTunnelConfigDir();
|
|
5577
|
+
if (!import_fs6.default.existsSync(dir)) {
|
|
5578
|
+
return [];
|
|
5579
|
+
}
|
|
5580
|
+
const files = import_fs6.default.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
5581
|
+
const configs = [];
|
|
5582
|
+
for (const file of files) {
|
|
5583
|
+
const config = readConfigFile(import_path6.default.join(dir, file));
|
|
5584
|
+
if (config && config.name && config.configId) {
|
|
5585
|
+
configs.push(config);
|
|
5586
|
+
}
|
|
5587
|
+
}
|
|
5588
|
+
return configs;
|
|
5589
|
+
}
|
|
5590
|
+
function findConfigFile(nameOrId) {
|
|
5591
|
+
const dir = getTunnelConfigDir();
|
|
5592
|
+
if (!import_fs6.default.existsSync(dir)) return null;
|
|
5593
|
+
const files = import_fs6.default.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
5594
|
+
const sanitized = sanitizeName(nameOrId);
|
|
5595
|
+
const nameMatch = files.find((f) => f.startsWith(sanitized + "_"));
|
|
5596
|
+
if (nameMatch) {
|
|
5597
|
+
const filePath = import_path6.default.join(dir, nameMatch);
|
|
5598
|
+
const config = readConfigFile(filePath);
|
|
5599
|
+
if (config && config.name === nameOrId) return { filePath, config };
|
|
5600
|
+
}
|
|
5601
|
+
const idCandidates = files.filter((f) => {
|
|
5602
|
+
const withoutExt = f.replace(/\.json$/, "");
|
|
5603
|
+
const lastUnderscore = withoutExt.indexOf("_");
|
|
5604
|
+
if (lastUnderscore === -1) return false;
|
|
5605
|
+
const idPart = withoutExt.slice(lastUnderscore + 1);
|
|
5606
|
+
return idPart.startsWith(nameOrId);
|
|
5607
|
+
});
|
|
5608
|
+
if (idCandidates.length === 1) {
|
|
5609
|
+
const filePath = import_path6.default.join(dir, idCandidates[0]);
|
|
5610
|
+
const config = readConfigFile(filePath);
|
|
5611
|
+
if (config) return { filePath, config };
|
|
5612
|
+
}
|
|
5613
|
+
return null;
|
|
5614
|
+
}
|
|
5615
|
+
function findConfigByName(name) {
|
|
5616
|
+
const resolved = findConfigFile(name);
|
|
5617
|
+
return resolved?.config.name === name ? resolved.config : null;
|
|
5618
|
+
}
|
|
5619
|
+
function findConfig(nameOrId) {
|
|
5620
|
+
return findConfigFile(nameOrId)?.config ?? null;
|
|
5621
|
+
}
|
|
5622
|
+
function saveConfig(name, configId, tunnelConfig, autoStart = false) {
|
|
5623
|
+
const nameErr = validateName(name);
|
|
5624
|
+
if (nameErr) {
|
|
5625
|
+
throw nameErr;
|
|
5626
|
+
}
|
|
5627
|
+
const existing = findConfigByName(name);
|
|
5628
|
+
if (existing) {
|
|
5629
|
+
throw new Error(
|
|
5630
|
+
`A tunnel config with the name "${name}" already exists (configId: ${existing.configId}). Please use a different name.`
|
|
5631
|
+
);
|
|
5632
|
+
}
|
|
5633
|
+
const dir = ensureTunnelConfigDir();
|
|
5634
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5635
|
+
const saved = {
|
|
5636
|
+
name,
|
|
5637
|
+
configId,
|
|
5638
|
+
autoStart,
|
|
5639
|
+
createdAt: now,
|
|
5640
|
+
updatedAt: now,
|
|
5641
|
+
tunnelConfig
|
|
5642
|
+
};
|
|
5643
|
+
const filename = buildFilename(sanitizeName(name), configId);
|
|
5644
|
+
const filePath = import_path6.default.join(dir, filename);
|
|
5645
|
+
import_fs6.default.writeFileSync(filePath, JSON.stringify(saved, null, 2), { encoding: "utf-8" });
|
|
5646
|
+
logger.info(`Config "${name}" saved to ${filePath}`);
|
|
5647
|
+
return saved;
|
|
5648
|
+
}
|
|
5649
|
+
function deleteConfig(nameOrId) {
|
|
5650
|
+
const resolved = findConfigFile(nameOrId);
|
|
5651
|
+
if (!resolved) return null;
|
|
5652
|
+
import_fs6.default.unlinkSync(resolved.filePath);
|
|
5653
|
+
logger.info(`Config "${resolved.config.name}" deleted.`);
|
|
5654
|
+
return resolved.config.name;
|
|
5655
|
+
}
|
|
5656
|
+
function updateConfigAutoStart(nameOrId, autoStart) {
|
|
5657
|
+
const resolved = findConfigFile(nameOrId);
|
|
5658
|
+
if (!resolved) return null;
|
|
5659
|
+
resolved.config.autoStart = autoStart;
|
|
5660
|
+
resolved.config.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5661
|
+
writeConfigFile(resolved.filePath, resolved.config);
|
|
5662
|
+
logger.info(`Config "${resolved.config.name}" auto-start set to ${autoStart}`);
|
|
5663
|
+
return resolved.config;
|
|
5664
|
+
}
|
|
5665
|
+
function updateTunnelConfig(nameOrId, tunnelConfig) {
|
|
5666
|
+
const resolved = findConfigFile(nameOrId);
|
|
5667
|
+
if (!resolved) return null;
|
|
5668
|
+
resolved.config.tunnelConfig = tunnelConfig;
|
|
5669
|
+
resolved.config.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5670
|
+
writeConfigFile(resolved.filePath, resolved.config);
|
|
5671
|
+
logger.info(`Config "${resolved.config.name}" tunnel configuration updated`);
|
|
5672
|
+
return resolved.config;
|
|
5673
|
+
}
|
|
5674
|
+
function getAutoStartConfigs() {
|
|
5675
|
+
return listSavedConfigs().filter((c) => c.autoStart);
|
|
5676
|
+
}
|
|
5677
|
+
function printConfigList() {
|
|
5678
|
+
const configs = listSavedConfigs();
|
|
5679
|
+
if (configs.length === 0) {
|
|
5680
|
+
console.log(import_picocolors5.default.yellow("No saved tunnel configs found."));
|
|
5681
|
+
console.log(import_picocolors5.default.gray(`Config directory: ${getTunnelConfigDir()}`));
|
|
5682
|
+
return;
|
|
5683
|
+
}
|
|
5684
|
+
const nameW = 20;
|
|
5685
|
+
const idW = 12;
|
|
5686
|
+
const typeW = 8;
|
|
5687
|
+
const fwdW = 25;
|
|
5688
|
+
const serverW = 22;
|
|
5689
|
+
const autoW = 10;
|
|
5690
|
+
const header = import_picocolors5.default.bold("Name".padEnd(nameW)) + import_picocolors5.default.bold("Config ID".padEnd(idW)) + import_picocolors5.default.bold("Type".padEnd(typeW)) + import_picocolors5.default.bold("Forwarding".padEnd(fwdW)) + import_picocolors5.default.bold("Server".padEnd(serverW)) + import_picocolors5.default.bold("Auto-start".padEnd(autoW));
|
|
5691
|
+
console.log("\n" + header);
|
|
5692
|
+
console.log(import_picocolors5.default.gray("\u2500".repeat(nameW + idW + typeW + fwdW + serverW + autoW)));
|
|
5693
|
+
for (const c of configs) {
|
|
5694
|
+
const tc = c.tunnelConfig;
|
|
5695
|
+
const forwarding = Array.isArray(tc.forwarding) ? tc.forwarding[0]?.address : String(tc.forwarding || "");
|
|
5696
|
+
const type = (Array.isArray(tc.forwarding) ? tc.forwarding[0]?.type : void 0) || "http";
|
|
5697
|
+
const server = tc.serverAddress || "a.pinggy.io";
|
|
5698
|
+
const line = import_picocolors5.default.cyanBright(c.name.padEnd(nameW)) + import_picocolors5.default.gray(c.configId.slice(0, 8).padEnd(idW)) + type.padEnd(typeW) + forwarding.slice(0, fwdW - 2).padEnd(fwdW) + server.slice(0, serverW - 2).padEnd(serverW) + (c.autoStart ? import_picocolors5.default.green("yes") : import_picocolors5.default.gray("no")).padEnd(autoW);
|
|
5699
|
+
console.log(line);
|
|
5700
|
+
}
|
|
5701
|
+
console.log();
|
|
5702
|
+
}
|
|
5703
|
+
function printConfigDetail(config) {
|
|
5704
|
+
console.log(import_picocolors5.default.bold(`
|
|
5705
|
+
Tunnel Config: ${import_picocolors5.default.cyanBright(config.name)}`));
|
|
5706
|
+
console.log(import_picocolors5.default.gray("\u2500".repeat(40)));
|
|
5707
|
+
console.log(` Config ID: ${config.configId}`);
|
|
5708
|
+
console.log(` Auto-start: ${config.autoStart ? import_picocolors5.default.green("yes") : import_picocolors5.default.gray("no")}`);
|
|
5709
|
+
console.log(` Created: ${config.createdAt}`);
|
|
5710
|
+
console.log(` Updated: ${config.updatedAt}`);
|
|
5711
|
+
console.log(import_picocolors5.default.gray("\u2500".repeat(40)));
|
|
5712
|
+
console.log(` Server: ${config.tunnelConfig.serverAddress || "a.pinggy.io"}`);
|
|
5713
|
+
console.log(` Token: ${config.tunnelConfig.token ? "***" + config.tunnelConfig.token.slice(-4) : "(none)"}`);
|
|
5714
|
+
const fwd = config.tunnelConfig.forwarding;
|
|
5715
|
+
if (Array.isArray(fwd)) {
|
|
5716
|
+
const defaultFwds = [];
|
|
5717
|
+
const customFwds = [];
|
|
5718
|
+
for (const f of fwd) {
|
|
5719
|
+
if (typeof f === "string") {
|
|
5720
|
+
defaultFwds.push(f);
|
|
5721
|
+
} else if (f.listenAddress) {
|
|
5722
|
+
customFwds.push(f);
|
|
5723
|
+
} else {
|
|
5724
|
+
defaultFwds.push(f);
|
|
5725
|
+
}
|
|
5726
|
+
}
|
|
5727
|
+
for (const f of defaultFwds) {
|
|
5728
|
+
const addr = typeof f === "string" ? f : `${f.address} (${f.type || "http"})`;
|
|
5729
|
+
console.log(` Forwarding: ${addr}`);
|
|
5730
|
+
if (config.tunnelConfig.webDebugger) {
|
|
5731
|
+
console.log(` Debugger: ${config.tunnelConfig.webDebugger}`);
|
|
5732
|
+
}
|
|
5733
|
+
}
|
|
5734
|
+
if (customFwds.length > 0) {
|
|
5735
|
+
console.log(import_picocolors5.default.gray("\u2500".repeat(40)));
|
|
5736
|
+
console.log(import_picocolors5.default.bold(" Domain Mappings:"));
|
|
5737
|
+
for (const f of customFwds) {
|
|
5738
|
+
if (typeof f === "string") continue;
|
|
5739
|
+
const domain = f.listenAddress;
|
|
5740
|
+
const target = f.address;
|
|
5741
|
+
const type = f.type || "http";
|
|
5742
|
+
console.log(` ${import_picocolors5.default.cyanBright(domain)} \u2192 ${target} (${type})`);
|
|
5743
|
+
}
|
|
5744
|
+
}
|
|
5745
|
+
} else if (fwd) {
|
|
5746
|
+
console.log(` Forwarding: ${fwd}`);
|
|
5747
|
+
}
|
|
5748
|
+
console.log();
|
|
5749
|
+
}
|
|
5750
|
+
var import_fs6, import_path6, import_picocolors5;
|
|
5751
|
+
var init_configStore = __esm({
|
|
5752
|
+
"src/cli/configStore.ts"() {
|
|
5753
|
+
"use strict";
|
|
5754
|
+
init_cjs_shims();
|
|
5755
|
+
import_fs6 = __toESM(require("fs"), 1);
|
|
5756
|
+
import_path6 = __toESM(require("path"), 1);
|
|
5757
|
+
init_configDir();
|
|
5758
|
+
init_logger();
|
|
5759
|
+
import_picocolors5 = __toESM(require("picocolors"), 1);
|
|
5760
|
+
}
|
|
5761
|
+
});
|
|
5762
|
+
|
|
5763
|
+
// src/cli/buildAndStartTunnel.ts
|
|
5764
|
+
async function buildAndStartTunnel(values, positionals, manager) {
|
|
5765
|
+
await initRemoteManagement(values);
|
|
5766
|
+
logger.debug("Building final config from CLI values and positionals", { values, positionals });
|
|
5767
|
+
const finalConfig = await buildFinalConfig(values, positionals);
|
|
5768
|
+
logger.debug("Final configuration built", finalConfig);
|
|
5769
|
+
if (values.save) {
|
|
5770
|
+
const name = values.name;
|
|
5771
|
+
if (!name) {
|
|
5772
|
+
printer_default.error("--save requires --name to specify a name for the tunnel config.");
|
|
5773
|
+
process.exit(1);
|
|
5774
|
+
}
|
|
5775
|
+
const nameErr = validateName(name);
|
|
5776
|
+
if (nameErr) {
|
|
5777
|
+
printer_default.error(nameErr.message);
|
|
5778
|
+
process.exit(1);
|
|
5779
|
+
}
|
|
5780
|
+
const autoStart = !!values.auto;
|
|
5781
|
+
saveConfig(name, finalConfig.configId, finalConfig, autoStart);
|
|
5782
|
+
printer_default.success(`Config "${name}" saved.`);
|
|
5783
|
+
}
|
|
5784
|
+
await startCli(finalConfig, manager);
|
|
5785
|
+
}
|
|
5786
|
+
async function initRemoteManagement(values) {
|
|
5787
|
+
const parseResult = await parseRemoteManagement(values);
|
|
5788
|
+
if (parseResult?.ok === false) {
|
|
5789
|
+
logger.error("Failed to initiate remote management:", parseResult.error);
|
|
5790
|
+
printer_default.fatal(parseResult.error);
|
|
5791
|
+
}
|
|
5792
|
+
}
|
|
5793
|
+
var init_buildAndStartTunnel = __esm({
|
|
5794
|
+
"src/cli/buildAndStartTunnel.ts"() {
|
|
5795
|
+
"use strict";
|
|
5796
|
+
init_cjs_shims();
|
|
5797
|
+
init_logger();
|
|
5798
|
+
init_remoteManagement();
|
|
5799
|
+
init_buildConfig();
|
|
5800
|
+
init_starCli();
|
|
5801
|
+
init_printer();
|
|
5802
|
+
init_configStore();
|
|
5803
|
+
}
|
|
5804
|
+
});
|
|
5805
|
+
|
|
5806
|
+
// src/cli/subcommands.ts
|
|
5807
|
+
function isSubcommand(rawArgs) {
|
|
5808
|
+
return rawArgs.length > 0 && SUBCOMMANDS.has(rawArgs[0]);
|
|
5809
|
+
}
|
|
5810
|
+
async function handleSubcommand(rawArgs, manager) {
|
|
5811
|
+
const sub = rawArgs[0];
|
|
5812
|
+
const rest = rawArgs.slice(1);
|
|
5813
|
+
switch (sub) {
|
|
5814
|
+
case "config":
|
|
5815
|
+
await handleConfig(rest);
|
|
5816
|
+
return;
|
|
5817
|
+
case "start":
|
|
5818
|
+
await handleStart(rest, manager);
|
|
5819
|
+
return;
|
|
5820
|
+
}
|
|
5821
|
+
}
|
|
5822
|
+
async function handleConfig(args) {
|
|
5823
|
+
if (args.length === 0) {
|
|
5824
|
+
printConfigHelp();
|
|
5825
|
+
return;
|
|
5826
|
+
}
|
|
5827
|
+
const verb = args[0];
|
|
5828
|
+
const rest = args.slice(1);
|
|
5829
|
+
switch (verb) {
|
|
5830
|
+
case "list":
|
|
5831
|
+
case "ls":
|
|
5832
|
+
printConfigList();
|
|
5833
|
+
return;
|
|
5834
|
+
case "show": {
|
|
5835
|
+
const names = requireNames(rest, "config show");
|
|
5836
|
+
for (const name of names) {
|
|
5837
|
+
const saved2 = resolveConfig(name);
|
|
5838
|
+
if (saved2) printConfigDetail(saved2);
|
|
5839
|
+
}
|
|
5840
|
+
return;
|
|
5841
|
+
}
|
|
5842
|
+
case "save": {
|
|
5843
|
+
const name = requireName(rest, "config save");
|
|
5844
|
+
await handleConfigSave(name, rest.slice(1));
|
|
5845
|
+
return;
|
|
5846
|
+
}
|
|
5847
|
+
case "delete": {
|
|
5848
|
+
const names = requireNames(rest, "config delete");
|
|
5849
|
+
for (const name of names) {
|
|
5850
|
+
const deletedName = deleteConfig(name);
|
|
5851
|
+
if (deletedName) {
|
|
5852
|
+
printer_default.success(`Config "${deletedName}" deleted.`);
|
|
5853
|
+
} else {
|
|
5854
|
+
printer_default.error(`No config found matching "${name}". Use: pinggy config list`);
|
|
5855
|
+
}
|
|
5856
|
+
}
|
|
5857
|
+
return;
|
|
5858
|
+
}
|
|
5859
|
+
case "update": {
|
|
5860
|
+
const name = requireName(rest, "config update");
|
|
5861
|
+
await handleConfigUpdate(name, rest.slice(1));
|
|
5862
|
+
return;
|
|
5863
|
+
}
|
|
5864
|
+
case "auto": {
|
|
5865
|
+
const names = requireNames(rest, "config auto");
|
|
5866
|
+
for (const name of names) {
|
|
5867
|
+
const updated = updateConfigAutoStart(name, true);
|
|
5868
|
+
if (updated) {
|
|
5869
|
+
printer_default.success(`Config "${updated.name}" auto-start set to on.`);
|
|
5870
|
+
} else {
|
|
5871
|
+
printer_default.error(`No config found matching "${name}". Use: pinggy config list`);
|
|
5872
|
+
}
|
|
5873
|
+
}
|
|
5874
|
+
return;
|
|
5875
|
+
}
|
|
5876
|
+
case "noauto": {
|
|
5877
|
+
const names = requireNames(rest, "config noauto");
|
|
5878
|
+
for (const name of names) {
|
|
5879
|
+
const updated = updateConfigAutoStart(name, false);
|
|
5880
|
+
if (updated) {
|
|
5881
|
+
printer_default.success(`Config "${updated.name}" auto-start set to off.`);
|
|
5882
|
+
} else {
|
|
5883
|
+
printer_default.error(`No config found matching "${name}". Use: pinggy config list`);
|
|
5884
|
+
}
|
|
5885
|
+
}
|
|
5886
|
+
return;
|
|
5887
|
+
}
|
|
5888
|
+
default:
|
|
5889
|
+
const saved = resolveConfig(verb);
|
|
5890
|
+
if (saved) printConfigDetail(saved);
|
|
5891
|
+
return;
|
|
5892
|
+
}
|
|
5893
|
+
}
|
|
5894
|
+
async function handleConfigSave(name, remainingArgs) {
|
|
5895
|
+
const nameErr = validateName(name);
|
|
5896
|
+
if (nameErr) {
|
|
5897
|
+
printer_default.error(nameErr.message);
|
|
5898
|
+
process.exit(1);
|
|
5899
|
+
}
|
|
5900
|
+
const { values, positionals } = parseCliArgs(cliOptions, remainingArgs);
|
|
5901
|
+
const autoStart = !!values.auto;
|
|
5902
|
+
logger.debug("Building config for save", { name, values, positionals });
|
|
5903
|
+
const finalConfig = await buildFinalConfig(values, positionals);
|
|
5904
|
+
saveConfig(name, finalConfig.configId, finalConfig, autoStart);
|
|
5905
|
+
printer_default.success(`Config "${name}" saved.`);
|
|
5906
|
+
}
|
|
5907
|
+
async function handleConfigUpdate(nameOrId, remainingArgs) {
|
|
5908
|
+
const saved = resolveConfig(nameOrId);
|
|
5909
|
+
if (!saved) return;
|
|
5910
|
+
const { values, positionals } = parseCliArgs(cliOptions, remainingArgs);
|
|
5911
|
+
logger.debug("Building updated config", { nameOrId, values, positionals });
|
|
5912
|
+
const updatedConfig = await buildFinalConfig(values, positionals, saved.tunnelConfig);
|
|
5913
|
+
const result = updateTunnelConfig(nameOrId, updatedConfig);
|
|
5914
|
+
if (result) {
|
|
5915
|
+
printer_default.success(`Config "${result.name}" updated.`);
|
|
5916
|
+
printConfigDetail(result);
|
|
5917
|
+
} else {
|
|
5918
|
+
printer_default.error(`Failed to update config "${nameOrId}".`);
|
|
5919
|
+
}
|
|
5920
|
+
}
|
|
5921
|
+
async function handleStart(args, manager) {
|
|
5922
|
+
const startAll = args.includes("--all");
|
|
5923
|
+
const argsWithoutAll = args.filter((a) => a !== "--all");
|
|
5924
|
+
const names = [];
|
|
5925
|
+
let i = 0;
|
|
5926
|
+
while (i < argsWithoutAll.length && !argsWithoutAll[i].startsWith("-")) {
|
|
5927
|
+
names.push(argsWithoutAll[i]);
|
|
5928
|
+
i++;
|
|
5929
|
+
}
|
|
5930
|
+
const flagArgs = argsWithoutAll.slice(i);
|
|
5931
|
+
const { values, positionals } = parseCliArgs(cliOptions, flagArgs);
|
|
5932
|
+
configureLogger(values);
|
|
5933
|
+
if (startAll) {
|
|
5934
|
+
await initRemoteManagementBackground(values);
|
|
5935
|
+
await startAutoStartTunnels(manager);
|
|
5936
|
+
return;
|
|
5937
|
+
}
|
|
5938
|
+
if (names.length === 0) {
|
|
5939
|
+
printStartHelp();
|
|
5940
|
+
return;
|
|
5941
|
+
}
|
|
5942
|
+
const resolved = [];
|
|
5943
|
+
for (const name of names) {
|
|
5944
|
+
const saved = resolveConfig(name);
|
|
5945
|
+
if (!saved) return;
|
|
5946
|
+
resolved.push(saved);
|
|
5947
|
+
}
|
|
5948
|
+
if (resolved.length > 1 && flagArgs.length > 0) {
|
|
5949
|
+
printer_default.error("Runtime overrides (-l, --type, etc.) can only be used when starting a single tunnel.");
|
|
5950
|
+
printer_default.print(" Start one tunnel: pinggy start my-tunnel -l 4000");
|
|
5951
|
+
printer_default.print(" Or update first: pinggy config update my-tunnel -l 4000");
|
|
5952
|
+
return;
|
|
5953
|
+
}
|
|
5954
|
+
await initRemoteManagementBackground(values);
|
|
5955
|
+
if (resolved.length === 1) {
|
|
5956
|
+
const saved = resolved[0];
|
|
5957
|
+
logger.debug("Building config with overrides", { name: saved.name });
|
|
5958
|
+
const finalConfig = await buildFinalConfig(values, positionals, saved.tunnelConfig);
|
|
5959
|
+
finalConfig.configId = saved.configId;
|
|
5960
|
+
await startCli(finalConfig, manager);
|
|
5961
|
+
} else {
|
|
5962
|
+
await startNamedTunnels(resolved, manager);
|
|
5963
|
+
}
|
|
5964
|
+
}
|
|
5965
|
+
async function startAutoStartTunnels(manager) {
|
|
5966
|
+
const configs = getAutoStartConfigs();
|
|
5967
|
+
if (configs.length === 0) {
|
|
5968
|
+
printer_default.warn("No configs marked for auto-start. Use: pinggy config auto <name>");
|
|
5969
|
+
return;
|
|
5970
|
+
}
|
|
5971
|
+
printer_default.print(import_picocolors6.default.cyanBright(`Starting ${configs.length} auto-start tunnel(s)...`));
|
|
5972
|
+
for (const saved of configs) {
|
|
5973
|
+
await startSavedTunnel(saved, manager);
|
|
5974
|
+
}
|
|
5975
|
+
printer_default.print(import_picocolors6.default.gray("\nAll auto-start tunnels launched. Press Ctrl+C to stop.\n"));
|
|
5976
|
+
await new Promise(() => {
|
|
5977
|
+
});
|
|
5978
|
+
}
|
|
5979
|
+
async function startNamedTunnels(configs, manager) {
|
|
5980
|
+
printer_default.print(import_picocolors6.default.cyanBright(`Starting ${configs.length} tunnel(s)...`));
|
|
5981
|
+
for (const saved of configs) {
|
|
5982
|
+
await startSavedTunnel(saved, manager);
|
|
5983
|
+
}
|
|
5984
|
+
printer_default.print(import_picocolors6.default.gray("\nAll tunnels launched. Press Ctrl+C to stop.\n"));
|
|
5985
|
+
await new Promise(() => {
|
|
5986
|
+
});
|
|
5987
|
+
}
|
|
5988
|
+
async function startSavedTunnel(saved, manager) {
|
|
5989
|
+
const config = {
|
|
5990
|
+
...saved.tunnelConfig,
|
|
5991
|
+
configId: saved.configId,
|
|
5992
|
+
name: saved.name,
|
|
5993
|
+
optional: {
|
|
5994
|
+
...saved.tunnelConfig.optional,
|
|
5995
|
+
noTui: true
|
|
5996
|
+
}
|
|
5997
|
+
};
|
|
5998
|
+
try {
|
|
5999
|
+
const tunnel = await manager.createTunnel(config);
|
|
6000
|
+
await manager.startTunnel(tunnel.tunnelid);
|
|
6001
|
+
const urls = await manager.getTunnelUrls(tunnel.tunnelid);
|
|
6002
|
+
printer_default.success(`"${saved.name}" started`);
|
|
6003
|
+
(urls ?? []).forEach(
|
|
6004
|
+
(url) => printer_default.print(" " + import_picocolors6.default.magentaBright(url))
|
|
6005
|
+
);
|
|
6006
|
+
manager.registerWorkerErrorListner(tunnel.tunnelid, (_id, error) => {
|
|
6007
|
+
printer_default.error(`[${saved.name}] Fatal: ${error.message}`);
|
|
6008
|
+
});
|
|
6009
|
+
manager.registerDisconnectListener(tunnel.tunnelid, async (_id, error, messages) => {
|
|
6010
|
+
if (error) printer_default.warn(`[${saved.name}] Disconnected: ${error}`);
|
|
6011
|
+
messages?.forEach((m) => printer_default.warn(`[${saved.name}] ${m}`));
|
|
6012
|
+
});
|
|
6013
|
+
manager.registerReconnectingListener(tunnel.tunnelid, (_id, retryCnt) => {
|
|
6014
|
+
printer_default.print(import_picocolors6.default.gray(`[${saved.name}] Reconnecting (attempt #${retryCnt})...`));
|
|
6015
|
+
});
|
|
6016
|
+
manager.registerReconnectionCompletedListener(tunnel.tunnelid, async (_id, urls2) => {
|
|
6017
|
+
printer_default.success(`[${saved.name}] Reconnected`);
|
|
6018
|
+
(urls2 ?? []).forEach(
|
|
6019
|
+
(url) => printer_default.print(" " + import_picocolors6.default.magentaBright(url))
|
|
6020
|
+
);
|
|
6021
|
+
});
|
|
6022
|
+
manager.registerReconnectionFailedListener(tunnel.tunnelid, (_id, retryCnt) => {
|
|
6023
|
+
printer_default.error(`[${saved.name}] Reconnection failed after ${retryCnt} attempts`);
|
|
6024
|
+
});
|
|
6025
|
+
} catch (err) {
|
|
6026
|
+
printer_default.error(`[${saved.name}] Failed to start: ${err.message || err}`);
|
|
6027
|
+
}
|
|
6028
|
+
}
|
|
6029
|
+
function resolveConfig(nameOrId) {
|
|
6030
|
+
const saved = findConfig(nameOrId);
|
|
6031
|
+
if (!saved) {
|
|
6032
|
+
printer_default.error(`No config found matching "${nameOrId}". Use: pinggy config list`);
|
|
6033
|
+
return null;
|
|
6034
|
+
}
|
|
6035
|
+
return saved;
|
|
6036
|
+
}
|
|
6037
|
+
function requireName(args, command) {
|
|
6038
|
+
if (args.length === 0 || args[0].startsWith("-")) {
|
|
6039
|
+
printer_default.error(`Tunnel name is required. Usage: pinggy ${command} <name>`);
|
|
6040
|
+
process.exit(1);
|
|
6041
|
+
}
|
|
6042
|
+
return args[0];
|
|
6043
|
+
}
|
|
6044
|
+
function requireNames(args, command) {
|
|
6045
|
+
const names = [];
|
|
6046
|
+
for (const arg of args) {
|
|
6047
|
+
if (arg.startsWith("-")) break;
|
|
6048
|
+
names.push(arg);
|
|
6049
|
+
}
|
|
6050
|
+
if (names.length === 0) {
|
|
6051
|
+
printer_default.error(`At least one tunnel name is required. Usage: pinggy ${command} <name> [name2 ...]`);
|
|
6052
|
+
process.exit(1);
|
|
6053
|
+
}
|
|
6054
|
+
return names;
|
|
6055
|
+
}
|
|
6056
|
+
async function initRemoteManagementBackground(values) {
|
|
6057
|
+
const rmToken = values["remote-management"];
|
|
6058
|
+
if (typeof rmToken === "string" && rmToken.trim().length > 0) {
|
|
6059
|
+
const manageHost = values["manage"];
|
|
6060
|
+
try {
|
|
6061
|
+
await startRemoteManagement({
|
|
6062
|
+
apiKey: rmToken,
|
|
6063
|
+
serverUrl: buildRemoteManagementWsUrl(manageHost)
|
|
6064
|
+
});
|
|
6065
|
+
} catch (e) {
|
|
6066
|
+
logger.error("Failed to initiate remote management:", e);
|
|
6067
|
+
printer_default.fatal(e);
|
|
6068
|
+
}
|
|
6069
|
+
}
|
|
6070
|
+
}
|
|
6071
|
+
function printConfigHelp() {
|
|
6072
|
+
console.log("\nUsage: pinggy config <command> [name] [options]\n");
|
|
6073
|
+
console.log("Commands:");
|
|
6074
|
+
console.log(" list List all saved configs");
|
|
6075
|
+
console.log(" show <name> Show config details");
|
|
6076
|
+
console.log(" save <name> [tunnel flags] Save a tunnel config");
|
|
6077
|
+
console.log(" update <name> [tunnel flags] Update a saved config");
|
|
6078
|
+
console.log(" delete <name> Delete a saved config");
|
|
6079
|
+
console.log(" auto <name> Enable auto-start");
|
|
6080
|
+
console.log(" noauto <name> Disable auto-start\n");
|
|
6081
|
+
}
|
|
6082
|
+
function printStartHelp() {
|
|
6083
|
+
console.log("\nUsage: pinggy start <name> [options]\n");
|
|
6084
|
+
console.log("Examples:");
|
|
6085
|
+
console.log(" pinggy start my-tunnel Start a saved tunnel");
|
|
6086
|
+
console.log(" pinggy start my-tunnel -l 4000 Start with override");
|
|
6087
|
+
console.log(" pinggy start tunnela tunnelb Start multiple tunnels");
|
|
6088
|
+
console.log(" pinggy start --all Start all auto-start tunnels\n");
|
|
6089
|
+
}
|
|
6090
|
+
var import_picocolors6, SUBCOMMANDS;
|
|
6091
|
+
var init_subcommands = __esm({
|
|
6092
|
+
"src/cli/subcommands.ts"() {
|
|
6093
|
+
"use strict";
|
|
6094
|
+
init_cjs_shims();
|
|
6095
|
+
init_options();
|
|
6096
|
+
init_parseArgs();
|
|
6097
|
+
init_buildConfig();
|
|
6098
|
+
init_starCli();
|
|
6099
|
+
init_printer();
|
|
6100
|
+
init_logger();
|
|
6101
|
+
import_picocolors6 = __toESM(require("picocolors"), 1);
|
|
6102
|
+
init_configStore();
|
|
6103
|
+
init_remoteManagement();
|
|
6104
|
+
SUBCOMMANDS = /* @__PURE__ */ new Set(["config", "start"]);
|
|
6105
|
+
}
|
|
6106
|
+
});
|
|
6107
|
+
|
|
5249
6108
|
// src/main.ts
|
|
5250
6109
|
var main_exports = {};
|
|
5251
6110
|
__export(main_exports, {
|
|
6111
|
+
RemoteManagementUnauthorizedError: () => RemoteManagementUnauthorizedError,
|
|
5252
6112
|
TunnelManager: () => TunnelManager,
|
|
5253
6113
|
TunnelOperations: () => TunnelOperations,
|
|
5254
6114
|
closeRemoteManagement: () => closeRemoteManagement,
|
|
@@ -5258,8 +6118,7 @@ __export(main_exports, {
|
|
|
5258
6118
|
});
|
|
5259
6119
|
async function main() {
|
|
5260
6120
|
try {
|
|
5261
|
-
const
|
|
5262
|
-
configureLogger(values);
|
|
6121
|
+
const rawArgs = process.argv.slice(2);
|
|
5263
6122
|
const manager = TunnelManager.getInstance();
|
|
5264
6123
|
process.on("SIGINT", () => {
|
|
5265
6124
|
logger.info("SIGINT received: stopping tunnels and exiting");
|
|
@@ -5268,6 +6127,12 @@ async function main() {
|
|
|
5268
6127
|
console.log("Tunnels stopped. Exiting.");
|
|
5269
6128
|
process.exit(0);
|
|
5270
6129
|
});
|
|
6130
|
+
if (isSubcommand(rawArgs)) {
|
|
6131
|
+
await handleSubcommand(rawArgs, manager);
|
|
6132
|
+
return;
|
|
6133
|
+
}
|
|
6134
|
+
const { values, positionals, hasAnyArgs } = parseCliArgs(cliOptions);
|
|
6135
|
+
configureLogger(values);
|
|
5271
6136
|
if (!hasAnyArgs || values.help) {
|
|
5272
6137
|
printHelpMessage();
|
|
5273
6138
|
return;
|
|
@@ -5276,22 +6141,13 @@ async function main() {
|
|
|
5276
6141
|
printer_default.print(`Pinggy CLI version: ${getVersion()}`);
|
|
5277
6142
|
return;
|
|
5278
6143
|
}
|
|
5279
|
-
|
|
5280
|
-
if (parseResult?.ok === false) {
|
|
5281
|
-
printer_default.error(parseResult.error);
|
|
5282
|
-
logger.error("Failed to initiate remote management:", parseResult.error);
|
|
5283
|
-
process.exit(1);
|
|
5284
|
-
}
|
|
5285
|
-
logger.debug("Building final config from CLI values and positionals", { values, positionals });
|
|
5286
|
-
const finalConfig = await buildFinalConfig(values, positionals);
|
|
5287
|
-
logger.debug("Final configuration built", finalConfig);
|
|
5288
|
-
await startCli(finalConfig, manager);
|
|
6144
|
+
await buildAndStartTunnel(values, positionals, manager);
|
|
5289
6145
|
} catch (error) {
|
|
5290
6146
|
logger.error("Unhandled error in CLI:", error);
|
|
5291
|
-
printer_default.
|
|
6147
|
+
printer_default.fatal(error);
|
|
5292
6148
|
}
|
|
5293
6149
|
}
|
|
5294
|
-
var import_url2, import_process,
|
|
6150
|
+
var import_url2, import_process, import_fs7, currentFile, entryFile;
|
|
5295
6151
|
var init_main = __esm({
|
|
5296
6152
|
"src/main.ts"() {
|
|
5297
6153
|
"use strict";
|
|
@@ -5299,23 +6155,22 @@ var init_main = __esm({
|
|
|
5299
6155
|
init_TunnelManager();
|
|
5300
6156
|
init_help();
|
|
5301
6157
|
init_options();
|
|
5302
|
-
init_buildConfig();
|
|
5303
6158
|
init_logger();
|
|
5304
|
-
init_remoteManagement();
|
|
5305
6159
|
init_parseArgs();
|
|
5306
6160
|
init_printer();
|
|
5307
|
-
init_starCli();
|
|
5308
6161
|
init_util();
|
|
5309
6162
|
init_handler();
|
|
5310
6163
|
import_url2 = require("url");
|
|
5311
6164
|
import_process = require("process");
|
|
5312
|
-
|
|
6165
|
+
import_fs7 = require("fs");
|
|
5313
6166
|
init_logger();
|
|
5314
6167
|
init_remoteManagement();
|
|
6168
|
+
init_buildAndStartTunnel();
|
|
6169
|
+
init_subcommands();
|
|
5315
6170
|
currentFile = (0, import_url2.fileURLToPath)(importMetaUrl);
|
|
5316
6171
|
entryFile = null;
|
|
5317
6172
|
try {
|
|
5318
|
-
entryFile = import_process.argv[1] ? (0,
|
|
6173
|
+
entryFile = import_process.argv[1] ? (0, import_fs7.realpathSync)(import_process.argv[1]) : null;
|
|
5319
6174
|
} catch (e) {
|
|
5320
6175
|
entryFile = null;
|
|
5321
6176
|
}
|
|
@@ -5328,6 +6183,7 @@ var init_main = __esm({
|
|
|
5328
6183
|
// src/index.ts
|
|
5329
6184
|
var index_exports = {};
|
|
5330
6185
|
__export(index_exports, {
|
|
6186
|
+
RemoteManagementUnauthorizedError: () => RemoteManagementUnauthorizedError,
|
|
5331
6187
|
TunnelErrorCodeType: () => TunnelErrorCodeType,
|
|
5332
6188
|
TunnelManager: () => TunnelManager,
|
|
5333
6189
|
TunnelOperations: () => TunnelOperations,
|
|
@@ -5336,7 +6192,8 @@ __export(index_exports, {
|
|
|
5336
6192
|
closeRemoteManagement: () => closeRemoteManagement,
|
|
5337
6193
|
enablePackageLogging: () => enablePackageLogging,
|
|
5338
6194
|
getRemoteManagementState: () => getRemoteManagementState,
|
|
5339
|
-
initiateRemoteManagement: () => initiateRemoteManagement
|
|
6195
|
+
initiateRemoteManagement: () => initiateRemoteManagement,
|
|
6196
|
+
startRemoteManagement: () => startRemoteManagement
|
|
5340
6197
|
});
|
|
5341
6198
|
module.exports = __toCommonJS(index_exports);
|
|
5342
6199
|
init_cjs_shims();
|
|
@@ -5442,11 +6299,11 @@ async function verifyAndLoad() {
|
|
|
5442
6299
|
await Promise.resolve().then(() => (init_main(), main_exports));
|
|
5443
6300
|
}
|
|
5444
6301
|
verifyAndLoad().catch((err) => {
|
|
5445
|
-
printer_default.
|
|
5446
|
-
process.exit(1);
|
|
6302
|
+
printer_default.fatal(`Failed to start CLI:, ${err}`);
|
|
5447
6303
|
});
|
|
5448
6304
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5449
6305
|
0 && (module.exports = {
|
|
6306
|
+
RemoteManagementUnauthorizedError,
|
|
5450
6307
|
TunnelErrorCodeType,
|
|
5451
6308
|
TunnelManager,
|
|
5452
6309
|
TunnelOperations,
|
|
@@ -5455,5 +6312,6 @@ verifyAndLoad().catch((err) => {
|
|
|
5455
6312
|
closeRemoteManagement,
|
|
5456
6313
|
enablePackageLogging,
|
|
5457
6314
|
getRemoteManagementState,
|
|
5458
|
-
initiateRemoteManagement
|
|
6315
|
+
initiateRemoteManagement,
|
|
6316
|
+
startRemoteManagement
|
|
5459
6317
|
});
|