pinggy 0.1.9 → 0.1.10
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/dist/index.cjs +452 -145
- package/dist/index.d.cts +145 -8
- package/dist/index.d.ts +145 -8
- package/dist/index.js +448 -144
- package/package.json +3 -2
- package/src/cli/buildConfig.ts +1 -0
- package/src/cli/defaults.ts +1 -0
- package/src/cli/options.ts +2 -0
- package/src/cli/starCli.tsx +119 -51
- package/src/index.ts +6 -2
- package/src/remote_management/handler.ts +48 -9
- package/src/remote_management/remoteManagement.ts +144 -83
- package/src/remote_management/remote_schema.ts +57 -32
- package/src/remote_management/websocket_handlers.ts +15 -1
- package/src/tunnel_manager/TunnelManager.ts +199 -29
- package/src/types.ts +27 -3
package/dist/index.cjs
CHANGED
|
@@ -847,7 +847,10 @@ var index_exports = {};
|
|
|
847
847
|
__export(index_exports, {
|
|
848
848
|
TunnelManager: () => TunnelManager,
|
|
849
849
|
TunnelOperations: () => TunnelOperations,
|
|
850
|
-
|
|
850
|
+
closeRemoteManagement: () => closeRemoteManagement,
|
|
851
|
+
enablePackageLogging: () => enablePackageLogging,
|
|
852
|
+
getRemoteManagementState: () => getRemoteManagementState,
|
|
853
|
+
initiateRemoteManagement: () => initiateRemoteManagement
|
|
851
854
|
});
|
|
852
855
|
module.exports = __toCommonJS(index_exports);
|
|
853
856
|
init_cjs_shims();
|
|
@@ -971,6 +974,7 @@ var TunnelManager = class _TunnelManager {
|
|
|
971
974
|
this.tunnelErrorListeners = /* @__PURE__ */ new Map();
|
|
972
975
|
this.tunnelDisconnectListeners = /* @__PURE__ */ new Map();
|
|
973
976
|
this.tunnelWorkerErrorListeners = /* @__PURE__ */ new Map();
|
|
977
|
+
this.tunnelStartListeners = /* @__PURE__ */ new Map();
|
|
974
978
|
}
|
|
975
979
|
static getInstance() {
|
|
976
980
|
if (!_TunnelManager.instance) {
|
|
@@ -1001,7 +1005,15 @@ var TunnelManager = class _TunnelManager {
|
|
|
1001
1005
|
throw new Error(`Tunnel with configId "${configid}" already exists`);
|
|
1002
1006
|
}
|
|
1003
1007
|
const tunnelid = config.tunnelid || await getUuid();
|
|
1004
|
-
|
|
1008
|
+
let instance;
|
|
1009
|
+
try {
|
|
1010
|
+
instance = await import_pinggy2.pinggy.createTunnel(config);
|
|
1011
|
+
} catch (e) {
|
|
1012
|
+
logger.error("Error creating tunnel instance:", e);
|
|
1013
|
+
throw e;
|
|
1014
|
+
}
|
|
1015
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1016
|
+
let autoReconnect = config.autoReconnect !== void 0 ? config.autoReconnect : false;
|
|
1005
1017
|
const managed = {
|
|
1006
1018
|
tunnelid,
|
|
1007
1019
|
configid,
|
|
@@ -1011,8 +1023,15 @@ var TunnelManager = class _TunnelManager {
|
|
|
1011
1023
|
additionalForwarding,
|
|
1012
1024
|
serve: config.serve,
|
|
1013
1025
|
warnings: [],
|
|
1014
|
-
isStopped: false
|
|
1026
|
+
isStopped: false,
|
|
1027
|
+
createdAt: now,
|
|
1028
|
+
startedAt: null,
|
|
1029
|
+
stoppedAt: null,
|
|
1030
|
+
autoReconnect
|
|
1015
1031
|
};
|
|
1032
|
+
instance.setPrimaryForwardingCallback(({}) => {
|
|
1033
|
+
managed.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1034
|
+
});
|
|
1016
1035
|
this.setupStatsCallback(tunnelid, managed);
|
|
1017
1036
|
this.setupErrorCallback(tunnelid, managed);
|
|
1018
1037
|
this.setupDisconnectCallback(tunnelid, managed);
|
|
@@ -1056,6 +1075,20 @@ var TunnelManager = class _TunnelManager {
|
|
|
1056
1075
|
if (managed.serve) {
|
|
1057
1076
|
this.startStaticFileServer(managed);
|
|
1058
1077
|
}
|
|
1078
|
+
try {
|
|
1079
|
+
const startListeners = this.tunnelStartListeners.get(tunnelId);
|
|
1080
|
+
if (startListeners) {
|
|
1081
|
+
for (const [id, listener] of startListeners) {
|
|
1082
|
+
try {
|
|
1083
|
+
listener(tunnelId, urls);
|
|
1084
|
+
} catch (err) {
|
|
1085
|
+
logger.debug("Error in start-listener callback", { listenerId: id, tunnelId, err });
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
} catch (e) {
|
|
1090
|
+
logger.warn("Failed to notify start listeners", { tunnelId, e });
|
|
1091
|
+
}
|
|
1059
1092
|
return urls;
|
|
1060
1093
|
}
|
|
1061
1094
|
/**
|
|
@@ -1085,9 +1118,11 @@ var TunnelManager = class _TunnelManager {
|
|
|
1085
1118
|
this.tunnelErrorListeners.delete(tunnelId);
|
|
1086
1119
|
this.tunnelDisconnectListeners.delete(tunnelId);
|
|
1087
1120
|
this.tunnelWorkerErrorListeners.delete(tunnelId);
|
|
1121
|
+
this.tunnelStartListeners.delete(tunnelId);
|
|
1088
1122
|
managed.serveWorker = null;
|
|
1089
1123
|
managed.warnings = managed.warnings ?? [];
|
|
1090
1124
|
managed.isStopped = true;
|
|
1125
|
+
managed.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1091
1126
|
logger.info("Tunnel stopped", { tunnelId, configId: managed.configid });
|
|
1092
1127
|
return { configid: managed.configid, tunnelid: managed.tunnelid };
|
|
1093
1128
|
} catch (error) {
|
|
@@ -1169,6 +1204,60 @@ var TunnelManager = class _TunnelManager {
|
|
|
1169
1204
|
this.tunnelStatsListeners.clear();
|
|
1170
1205
|
logger.info("All tunnels stopped and cleared");
|
|
1171
1206
|
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Remove a stopped tunnel's records so it will no longer be returned by list methods.
|
|
1209
|
+
*
|
|
1210
|
+
*
|
|
1211
|
+
* @param tunnelId - the tunnel id to remove
|
|
1212
|
+
* @returns true if the record was removed, false otherwise
|
|
1213
|
+
*/
|
|
1214
|
+
removeStoppedTunnelByTunnelId(tunnelId) {
|
|
1215
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
1216
|
+
if (!managed) {
|
|
1217
|
+
logger.debug("Attempted to remove non-existent tunnel", { tunnelId });
|
|
1218
|
+
return false;
|
|
1219
|
+
}
|
|
1220
|
+
if (!managed.isStopped) {
|
|
1221
|
+
logger.warn("Attempted to remove tunnel that is not stopped", { tunnelId });
|
|
1222
|
+
return false;
|
|
1223
|
+
}
|
|
1224
|
+
this._cleanupTunnelRecords(managed);
|
|
1225
|
+
logger.info("Removed stopped tunnel records", { tunnelId, configId: managed.configid });
|
|
1226
|
+
return true;
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Remove a stopped tunnel by its config id.
|
|
1230
|
+
* @param configId - the config id to remove
|
|
1231
|
+
* @returns true if the record was removed, false otherwise
|
|
1232
|
+
*/
|
|
1233
|
+
removeStoppedTunnelByConfigId(configId) {
|
|
1234
|
+
const managed = this.tunnelsByConfigId.get(configId);
|
|
1235
|
+
if (!managed) {
|
|
1236
|
+
logger.debug("Attempted to remove non-existent tunnel by configId", { configId });
|
|
1237
|
+
return false;
|
|
1238
|
+
}
|
|
1239
|
+
return this.removeStoppedTunnelByTunnelId(managed.tunnelid);
|
|
1240
|
+
}
|
|
1241
|
+
_cleanupTunnelRecords(managed) {
|
|
1242
|
+
if (!managed.isStopped) {
|
|
1243
|
+
throw new Error(`Active tunnel "${managed.tunnelid}" cannot be removed`);
|
|
1244
|
+
}
|
|
1245
|
+
try {
|
|
1246
|
+
if (managed.serveWorker) {
|
|
1247
|
+
managed.serveWorker = null;
|
|
1248
|
+
}
|
|
1249
|
+
this.tunnelStats.delete(managed.tunnelid);
|
|
1250
|
+
this.tunnelStatsListeners.delete(managed.tunnelid);
|
|
1251
|
+
this.tunnelErrorListeners.delete(managed.tunnelid);
|
|
1252
|
+
this.tunnelDisconnectListeners.delete(managed.tunnelid);
|
|
1253
|
+
this.tunnelWorkerErrorListeners.delete(managed.tunnelid);
|
|
1254
|
+
this.tunnelStartListeners.delete(managed.tunnelid);
|
|
1255
|
+
this.tunnelsByTunnelId.delete(managed.tunnelid);
|
|
1256
|
+
this.tunnelsByConfigId.delete(managed.configid);
|
|
1257
|
+
} catch (e) {
|
|
1258
|
+
logger.warn("Failed cleaning up tunnel records", { tunnelId: managed.tunnelid, error: e });
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1172
1261
|
/**
|
|
1173
1262
|
* Get tunnel instance by either configId or tunnelId
|
|
1174
1263
|
* @param configId - The configuration ID of the tunnel
|
|
@@ -1243,6 +1332,9 @@ var TunnelManager = class _TunnelManager {
|
|
|
1243
1332
|
additionalForwarding,
|
|
1244
1333
|
tunnelName
|
|
1245
1334
|
});
|
|
1335
|
+
if (existingTunnel.createdAt) {
|
|
1336
|
+
newTunnel.createdAt = existingTunnel.createdAt;
|
|
1337
|
+
}
|
|
1246
1338
|
this.startTunnel(newTunnel.tunnelid);
|
|
1247
1339
|
} catch (error) {
|
|
1248
1340
|
logger.error("Failed to restart tunnel", {
|
|
@@ -1387,13 +1479,26 @@ var TunnelManager = class _TunnelManager {
|
|
|
1387
1479
|
const stats = this.tunnelStats.get(tunnelId);
|
|
1388
1480
|
return stats || null;
|
|
1389
1481
|
}
|
|
1482
|
+
getLatestTunnelStats(tunnelId) {
|
|
1483
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
1484
|
+
if (!managed) {
|
|
1485
|
+
return null;
|
|
1486
|
+
}
|
|
1487
|
+
const stats = this.tunnelStats.get(tunnelId);
|
|
1488
|
+
if (stats && stats.length > 0) {
|
|
1489
|
+
return stats[stats.length - 1];
|
|
1490
|
+
}
|
|
1491
|
+
return null;
|
|
1492
|
+
}
|
|
1390
1493
|
/**
|
|
1391
1494
|
* Registers a listener function to receive tunnel statistics updates.
|
|
1392
1495
|
* The listener will be called whenever any tunnel's stats are updated.
|
|
1393
1496
|
*
|
|
1394
1497
|
* @param tunnelId - The tunnel ID to listen to stats for
|
|
1395
1498
|
* @param listener - Function that receives tunnelId and stats when updates occur
|
|
1396
|
-
* @returns A unique listener ID that can be used to deregister the listener
|
|
1499
|
+
* @returns A unique listener ID that can be used to deregister the listener and tunnelId
|
|
1500
|
+
*
|
|
1501
|
+
* @throws {Error} When the specified tunnelId does not exist
|
|
1397
1502
|
*/
|
|
1398
1503
|
async registerStatsListener(tunnelId, listener) {
|
|
1399
1504
|
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
@@ -1407,7 +1512,7 @@ var TunnelManager = class _TunnelManager {
|
|
|
1407
1512
|
const tunnelListeners = this.tunnelStatsListeners.get(tunnelId);
|
|
1408
1513
|
tunnelListeners.set(listenerId, listener);
|
|
1409
1514
|
logger.info("Stats listener registered for tunnel", { tunnelId, listenerId });
|
|
1410
|
-
return listenerId;
|
|
1515
|
+
return [listenerId, tunnelId];
|
|
1411
1516
|
}
|
|
1412
1517
|
async registerErrorListener(tunnelId, listener) {
|
|
1413
1518
|
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
@@ -1450,6 +1555,20 @@ var TunnelManager = class _TunnelManager {
|
|
|
1450
1555
|
tunnelWorkerErrorListner?.set(listenerId, listener);
|
|
1451
1556
|
logger.info("TunnelWorker error listener registered for tunnel", { tunnelId, listenerId });
|
|
1452
1557
|
}
|
|
1558
|
+
async registerStartListener(tunnelId, listener) {
|
|
1559
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
1560
|
+
if (!managed) {
|
|
1561
|
+
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
1562
|
+
}
|
|
1563
|
+
if (!this.tunnelStartListeners.has(tunnelId)) {
|
|
1564
|
+
this.tunnelStartListeners.set(tunnelId, /* @__PURE__ */ new Map());
|
|
1565
|
+
}
|
|
1566
|
+
const listenerId = await getUuid();
|
|
1567
|
+
const listeners = this.tunnelStartListeners.get(tunnelId);
|
|
1568
|
+
listeners.set(listenerId, listener);
|
|
1569
|
+
logger.info("Start listener registered for tunnel", { tunnelId, listenerId });
|
|
1570
|
+
return listenerId;
|
|
1571
|
+
}
|
|
1453
1572
|
/**
|
|
1454
1573
|
* Removes a previously registered stats listener.
|
|
1455
1574
|
*
|
|
@@ -1556,9 +1675,9 @@ var TunnelManager = class _TunnelManager {
|
|
|
1556
1675
|
}
|
|
1557
1676
|
setupErrorCallback(tunnelId, managed) {
|
|
1558
1677
|
try {
|
|
1559
|
-
const callback = (errorNo,
|
|
1678
|
+
const callback = ({ errorNo, error, recoverable }) => {
|
|
1560
1679
|
try {
|
|
1561
|
-
const msg = typeof
|
|
1680
|
+
const msg = typeof error === "string" ? error : String(error);
|
|
1562
1681
|
const isFatal = true;
|
|
1563
1682
|
logger.debug("Tunnel reported error", { tunnelId, errorNo, errorMsg: msg, recoverable });
|
|
1564
1683
|
this.notifyErrorListeners(tunnelId, msg, isFatal);
|
|
@@ -1574,9 +1693,25 @@ var TunnelManager = class _TunnelManager {
|
|
|
1574
1693
|
}
|
|
1575
1694
|
setupDisconnectCallback(tunnelId, managed) {
|
|
1576
1695
|
try {
|
|
1577
|
-
const callback = (error, messages) => {
|
|
1696
|
+
const callback = ({ error, messages }) => {
|
|
1578
1697
|
try {
|
|
1579
1698
|
logger.debug("Tunnel disconnected", { tunnelId, error, messages });
|
|
1699
|
+
const managedTunnel = this.tunnelsByTunnelId.get(tunnelId);
|
|
1700
|
+
if (managedTunnel) {
|
|
1701
|
+
managedTunnel.isStopped = true;
|
|
1702
|
+
managedTunnel.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1703
|
+
}
|
|
1704
|
+
if (managedTunnel && managedTunnel.autoReconnect) {
|
|
1705
|
+
logger.info("Auto-reconnecting tunnel", { tunnelId });
|
|
1706
|
+
setTimeout(async () => {
|
|
1707
|
+
try {
|
|
1708
|
+
await this.restartTunnel(tunnelId);
|
|
1709
|
+
logger.info("Tunnel auto-reconnected successfully", { tunnelId });
|
|
1710
|
+
} catch (e) {
|
|
1711
|
+
logger.error("Failed to auto-reconnect tunnel", { tunnelId, e });
|
|
1712
|
+
}
|
|
1713
|
+
}, 1e4);
|
|
1714
|
+
}
|
|
1580
1715
|
const listeners = this.tunnelDisconnectListeners.get(tunnelId);
|
|
1581
1716
|
if (!listeners) return;
|
|
1582
1717
|
for (const [id, listener] of listeners) {
|
|
@@ -1738,6 +1873,7 @@ var cliOptions = {
|
|
|
1738
1873
|
v: { type: "boolean", description: "Print logs to stdout for Cli. Overrides PINGGY_LOG_STDOUT environment variable" },
|
|
1739
1874
|
vv: { type: "boolean", description: "Enable detailed logging for the Node.js SDK and Libpinggy, including both info and debug level logs." },
|
|
1740
1875
|
vvv: { type: "boolean", description: "Enable all logs from Cli, SDK and internal components." },
|
|
1876
|
+
autoreconnect: { type: "boolean", short: "a", description: "Automatically reconnect tunnel on failure." },
|
|
1741
1877
|
// Save and load config
|
|
1742
1878
|
saveconf: { type: "string", description: "Create the configuration file based on the options provided here" },
|
|
1743
1879
|
conf: { type: "string", description: "Use the configuration file as base. Other options will be used to override this file" },
|
|
@@ -1812,7 +1948,8 @@ var defaultOptions = {
|
|
|
1812
1948
|
httpsOnly: false,
|
|
1813
1949
|
originalRequestUrl: false,
|
|
1814
1950
|
allowPreflight: false,
|
|
1815
|
-
reverseProxy: false
|
|
1951
|
+
reverseProxy: false,
|
|
1952
|
+
autoReconnect: false
|
|
1816
1953
|
};
|
|
1817
1954
|
|
|
1818
1955
|
// src/cli/extendedOptions.ts
|
|
@@ -2235,7 +2372,8 @@ async function buildFinalConfig(values, positionals) {
|
|
|
2235
2372
|
serverAddress: server || defaultOptions.serverAddress,
|
|
2236
2373
|
tunnelType: initialTunnel ? [initialTunnel] : defaultOptions.tunnelType,
|
|
2237
2374
|
NoTUI: values.notui || false,
|
|
2238
|
-
qrCode: qrCode || false
|
|
2375
|
+
qrCode: qrCode || false,
|
|
2376
|
+
autoReconnect: values.autoreconnect || false
|
|
2239
2377
|
};
|
|
2240
2378
|
parseType(finalConfig, values, type);
|
|
2241
2379
|
parseToken(finalConfig, token || values.token);
|
|
@@ -2335,15 +2473,23 @@ function newStatus(tunnelState, errorCode, errorMsg) {
|
|
|
2335
2473
|
};
|
|
2336
2474
|
}
|
|
2337
2475
|
function newStats() {
|
|
2338
|
-
return
|
|
2476
|
+
return {
|
|
2339
2477
|
numLiveConnections: 0,
|
|
2340
2478
|
numTotalConnections: 0,
|
|
2341
2479
|
numTotalReqBytes: 0,
|
|
2342
2480
|
numTotalResBytes: 0,
|
|
2343
2481
|
numTotalTxBytes: 0,
|
|
2344
2482
|
elapsedTime: 0
|
|
2345
|
-
}
|
|
2483
|
+
};
|
|
2346
2484
|
}
|
|
2485
|
+
var RemoteManagementStatus = {
|
|
2486
|
+
Connecting: "CONNECTING",
|
|
2487
|
+
Disconnecting: "DISCONNECTING",
|
|
2488
|
+
Reconnecting: "RECONNECTING",
|
|
2489
|
+
Running: "RUNNING",
|
|
2490
|
+
NotRunning: "NOT_RUNNING",
|
|
2491
|
+
Error: "ERROR"
|
|
2492
|
+
};
|
|
2347
2493
|
|
|
2348
2494
|
// src/remote_management/handler.ts
|
|
2349
2495
|
init_cjs_shims();
|
|
@@ -2363,7 +2509,10 @@ var AdditionalForwardingSchema = import_zod.z.object({
|
|
|
2363
2509
|
localPort: import_zod.z.number()
|
|
2364
2510
|
});
|
|
2365
2511
|
var TunnelConfigSchema = import_zod.z.object({
|
|
2366
|
-
allowPreflight: import_zod.z.boolean(),
|
|
2512
|
+
allowPreflight: import_zod.z.boolean().optional(),
|
|
2513
|
+
// primary key
|
|
2514
|
+
allowpreflight: import_zod.z.boolean().optional(),
|
|
2515
|
+
// legacy key
|
|
2367
2516
|
autoreconnect: import_zod.z.boolean(),
|
|
2368
2517
|
basicauth: import_zod.z.array(import_zod.z.object({ username: import_zod.z.string(), password: import_zod.z.string() })).nullable(),
|
|
2369
2518
|
bearerauth: import_zod.z.string().nullable(),
|
|
@@ -2387,12 +2536,30 @@ var TunnelConfigSchema = import_zod.z.object({
|
|
|
2387
2536
|
statusCheckInterval: import_zod.z.number(),
|
|
2388
2537
|
token: import_zod.z.string(),
|
|
2389
2538
|
tunnelTimeout: import_zod.z.number(),
|
|
2390
|
-
type: import_zod.z.enum([
|
|
2539
|
+
type: import_zod.z.enum([
|
|
2540
|
+
import_pinggy5.TunnelType.Http,
|
|
2541
|
+
import_pinggy5.TunnelType.Tcp,
|
|
2542
|
+
import_pinggy5.TunnelType.Udp,
|
|
2543
|
+
import_pinggy5.TunnelType.Tls,
|
|
2544
|
+
import_pinggy5.TunnelType.TlsTcp
|
|
2545
|
+
]),
|
|
2391
2546
|
webdebuggerport: import_zod.z.number(),
|
|
2392
2547
|
xff: import_zod.z.string(),
|
|
2393
2548
|
additionalForwarding: import_zod.z.array(AdditionalForwardingSchema).optional(),
|
|
2394
2549
|
serve: import_zod.z.string().optional()
|
|
2395
|
-
})
|
|
2550
|
+
}).superRefine((data, ctx) => {
|
|
2551
|
+
if (data.allowPreflight === void 0 && data.allowpreflight === void 0) {
|
|
2552
|
+
ctx.addIssue({
|
|
2553
|
+
code: "custom",
|
|
2554
|
+
message: "Either allowPreflight or allowpreflight is required",
|
|
2555
|
+
path: ["allowPreflight"]
|
|
2556
|
+
});
|
|
2557
|
+
}
|
|
2558
|
+
}).transform((data) => ({
|
|
2559
|
+
...data,
|
|
2560
|
+
allowPreflight: data.allowPreflight ?? data.allowpreflight,
|
|
2561
|
+
allowpreflight: data.allowPreflight ?? data.allowpreflight
|
|
2562
|
+
}));
|
|
2396
2563
|
var StartSchema = import_zod.z.object({
|
|
2397
2564
|
tunnelID: import_zod.z.string().uuid().nullable().optional(),
|
|
2398
2565
|
tunnelConfig: TunnelConfigSchema
|
|
@@ -2422,6 +2589,7 @@ function tunnelConfigToPinggyOptions(config) {
|
|
|
2422
2589
|
allowPreflight: config.allowPreflight,
|
|
2423
2590
|
reverseProxy: config.noReverseProxy,
|
|
2424
2591
|
force: config.force,
|
|
2592
|
+
autoReconnect: config.autoreconnect,
|
|
2425
2593
|
optional: {
|
|
2426
2594
|
sniServerName: config.localservertlssni || ""
|
|
2427
2595
|
}
|
|
@@ -2435,7 +2603,8 @@ function pinggyOptionsToTunnelConfig(opts, configid, configName, localserverTls,
|
|
|
2435
2603
|
const parsedTokens = opts.bearerTokenAuth ? Array.isArray(opts.bearerTokenAuth) ? opts.bearerTokenAuth : JSON.parse(opts.bearerTokenAuth) : [];
|
|
2436
2604
|
return {
|
|
2437
2605
|
allowPreflight: opts.allowPreflight ?? false,
|
|
2438
|
-
|
|
2606
|
+
allowpreflight: opts.allowPreflight ?? false,
|
|
2607
|
+
autoreconnect: opts.autoReconnect ?? false,
|
|
2439
2608
|
basicauth: opts.basicAuth && Object.keys(opts.basicAuth).length ? opts.basicAuth : null,
|
|
2440
2609
|
bearerauth: parsedTokens.length ? parsedTokens.join(",") : null,
|
|
2441
2610
|
configid,
|
|
@@ -2472,11 +2641,24 @@ var TunnelOperations = class {
|
|
|
2472
2641
|
constructor() {
|
|
2473
2642
|
this.tunnelManager = TunnelManager.getInstance();
|
|
2474
2643
|
}
|
|
2644
|
+
buildStatus(tunnelId, state, errorCode) {
|
|
2645
|
+
const status = newStatus(state, errorCode, "");
|
|
2646
|
+
try {
|
|
2647
|
+
const managed = this.tunnelManager.getManagedTunnel("", tunnelId);
|
|
2648
|
+
if (managed) {
|
|
2649
|
+
status.createdtimestamp = managed.createdAt || "";
|
|
2650
|
+
status.starttimestamp = managed.startedAt || "";
|
|
2651
|
+
status.endtimestamp = managed.stoppedAt || "";
|
|
2652
|
+
}
|
|
2653
|
+
} catch (e) {
|
|
2654
|
+
}
|
|
2655
|
+
return status;
|
|
2656
|
+
}
|
|
2475
2657
|
// --- Helper to construct TunnelResponse ---
|
|
2476
2658
|
async buildTunnelResponse(tunnelid, tunnelConfig, configid, tunnelName, additionalForwarding, serve) {
|
|
2477
2659
|
const [status, stats, tlsInfo, greetMsg, remoteurls] = await Promise.all([
|
|
2478
2660
|
this.tunnelManager.getTunnelStatus(tunnelid),
|
|
2479
|
-
this.tunnelManager.
|
|
2661
|
+
this.tunnelManager.getLatestTunnelStats(tunnelid) || newStats(),
|
|
2480
2662
|
this.tunnelManager.getLocalserverTlsInfo(tunnelid),
|
|
2481
2663
|
this.tunnelManager.getTunnelGreetMessage(tunnelid),
|
|
2482
2664
|
this.tunnelManager.getTunnelUrls(tunnelid)
|
|
@@ -2485,7 +2667,7 @@ var TunnelOperations = class {
|
|
|
2485
2667
|
tunnelid,
|
|
2486
2668
|
remoteurls,
|
|
2487
2669
|
tunnelconfig: pinggyOptionsToTunnelConfig(tunnelConfig, configid, tunnelName, tlsInfo, greetMsg, additionalForwarding),
|
|
2488
|
-
status:
|
|
2670
|
+
status: this.buildStatus(tunnelid, status, "" /* NoError */),
|
|
2489
2671
|
stats
|
|
2490
2672
|
};
|
|
2491
2673
|
}
|
|
@@ -2540,8 +2722,7 @@ var TunnelOperations = class {
|
|
|
2540
2722
|
}
|
|
2541
2723
|
return Promise.all(
|
|
2542
2724
|
tunnels.map(async (t) => {
|
|
2543
|
-
const rawStats = this.tunnelManager.
|
|
2544
|
-
const stats = rawStats ?? newStats();
|
|
2725
|
+
const rawStats = this.tunnelManager.getLatestTunnelStats(t.tunnelid) || newStats();
|
|
2545
2726
|
const [status, tlsInfo, greetMsg] = await Promise.all([
|
|
2546
2727
|
this.tunnelManager.getTunnelStatus(t.tunnelid),
|
|
2547
2728
|
this.tunnelManager.getLocalserverTlsInfo(t.tunnelid),
|
|
@@ -2552,8 +2733,8 @@ var TunnelOperations = class {
|
|
|
2552
2733
|
return {
|
|
2553
2734
|
tunnelid: t.tunnelid,
|
|
2554
2735
|
remoteurls: t.remoteurls,
|
|
2555
|
-
status:
|
|
2556
|
-
stats,
|
|
2736
|
+
status: this.buildStatus(t.tunnelid, status, "" /* NoError */),
|
|
2737
|
+
stats: rawStats,
|
|
2557
2738
|
tunnelconfig: tunnelConfig
|
|
2558
2739
|
};
|
|
2559
2740
|
})
|
|
@@ -2601,13 +2782,30 @@ var TunnelOperations = class {
|
|
|
2601
2782
|
try {
|
|
2602
2783
|
const stats = this.tunnelManager.getTunnelStats(tunnelid);
|
|
2603
2784
|
if (!stats) {
|
|
2604
|
-
return newStats();
|
|
2785
|
+
return [newStats()];
|
|
2605
2786
|
}
|
|
2606
2787
|
return stats;
|
|
2607
2788
|
} catch (err) {
|
|
2608
2789
|
return this.error(ErrorCode.TunnelNotFound, err, "Failed to get tunnel stats");
|
|
2609
2790
|
}
|
|
2610
2791
|
}
|
|
2792
|
+
handleRegisterDisconnectListener(tunnelid, listener) {
|
|
2793
|
+
this.tunnelManager.registerDisconnectListener(tunnelid, listener);
|
|
2794
|
+
}
|
|
2795
|
+
handleRemoveStoppedTunnelByConfigId(configId) {
|
|
2796
|
+
try {
|
|
2797
|
+
return this.tunnelManager.removeStoppedTunnelByConfigId(configId);
|
|
2798
|
+
} catch (err) {
|
|
2799
|
+
return this.error(ErrorCode.InternalServerError, err, "Failed to remove stopped tunnel by configId");
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
handleRemoveStoppedTunnelByTunnelId(tunnelId) {
|
|
2803
|
+
try {
|
|
2804
|
+
return this.tunnelManager.removeStoppedTunnelByTunnelId(tunnelId);
|
|
2805
|
+
} catch (err) {
|
|
2806
|
+
return this.error(ErrorCode.InternalServerError, err, "Failed to remove stopped tunnel by tunnelId");
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2611
2809
|
};
|
|
2612
2810
|
|
|
2613
2811
|
// src/remote_management/websocket_handlers.ts
|
|
@@ -2640,11 +2838,13 @@ var WebSocketCommandHandler = class {
|
|
|
2640
2838
|
}
|
|
2641
2839
|
async handleStartReq(req, raw) {
|
|
2642
2840
|
const dc = StartSchema.parse(raw);
|
|
2841
|
+
printer_default.info("Starting tunnel with config name: " + dc.tunnelConfig.configname);
|
|
2643
2842
|
const result = await this.tunnelHandler.handleStart(dc.tunnelConfig);
|
|
2644
2843
|
return this.wrapResponse(result, req);
|
|
2645
2844
|
}
|
|
2646
2845
|
async handleStopReq(req, raw) {
|
|
2647
2846
|
const dc = StopSchema.parse(raw);
|
|
2847
|
+
printer_default.info("Stopping tunnel with ID: " + dc.tunnelID);
|
|
2648
2848
|
const result = await this.tunnelHandler.handleStop(dc.tunnelID);
|
|
2649
2849
|
return this.wrapResponse(result, req);
|
|
2650
2850
|
}
|
|
@@ -2674,7 +2874,17 @@ var WebSocketCommandHandler = class {
|
|
|
2674
2874
|
errResp.requestid = req.requestid;
|
|
2675
2875
|
return errResp;
|
|
2676
2876
|
}
|
|
2677
|
-
const
|
|
2877
|
+
const finalResult = JSON.parse(JSON.stringify(result));
|
|
2878
|
+
if (Array.isArray(finalResult)) {
|
|
2879
|
+
finalResult.forEach((item) => {
|
|
2880
|
+
if (item?.tunnelconfig) {
|
|
2881
|
+
delete item.tunnelconfig.allowPreflight;
|
|
2882
|
+
}
|
|
2883
|
+
});
|
|
2884
|
+
} else if (finalResult?.tunnelconfig) {
|
|
2885
|
+
delete finalResult.tunnelconfig.allowPreflight;
|
|
2886
|
+
}
|
|
2887
|
+
const respObj = NewResponseObject(finalResult);
|
|
2678
2888
|
respObj.command = req.command;
|
|
2679
2889
|
respObj.requestid = req.requestid;
|
|
2680
2890
|
return respObj;
|
|
@@ -2747,6 +2957,12 @@ function handleConnectionStatusMessage(firstMessage) {
|
|
|
2747
2957
|
// src/remote_management/remoteManagement.ts
|
|
2748
2958
|
var RECONNECT_SLEEP_MS = 5e3;
|
|
2749
2959
|
var PING_INTERVAL_MS = 3e4;
|
|
2960
|
+
var _remoteManagementState = {
|
|
2961
|
+
status: "NOT_RUNNING",
|
|
2962
|
+
errorMessage: ""
|
|
2963
|
+
};
|
|
2964
|
+
var _stopRequested = false;
|
|
2965
|
+
var currentWs = null;
|
|
2750
2966
|
function buildRemoteManagementWsUrl(manage) {
|
|
2751
2967
|
let baseUrl = (manage || "dashboard.pinggy.io").trim();
|
|
2752
2968
|
if (!(baseUrl.startsWith("ws://") || baseUrl.startsWith("wss://"))) {
|
|
@@ -2780,95 +2996,133 @@ async function parseRemoteManagement(values) {
|
|
|
2780
2996
|
}
|
|
2781
2997
|
}
|
|
2782
2998
|
async function initiateRemoteManagement(token, manage) {
|
|
2999
|
+
await printer_default.ensureDeps();
|
|
3000
|
+
await loadChalk();
|
|
2783
3001
|
if (!token || token.trim().length === 0) {
|
|
2784
3002
|
throw new Error("Remote management token is required (use --remote-management <TOKEN>)");
|
|
2785
3003
|
}
|
|
2786
3004
|
const wsUrl = buildRemoteManagementWsUrl(manage);
|
|
2787
3005
|
const wsHost = extractHostname(wsUrl);
|
|
2788
3006
|
logger.info("Remote management mode enabled.");
|
|
2789
|
-
|
|
3007
|
+
_stopRequested = false;
|
|
2790
3008
|
const sigintHandler = () => {
|
|
2791
|
-
|
|
3009
|
+
_stopRequested = true;
|
|
2792
3010
|
};
|
|
2793
3011
|
process.once("SIGINT", sigintHandler);
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
printer_default.print(`Connecting to ${wsHost}`);
|
|
2806
|
-
logger.info("Connecting to remote management", { wsUrl });
|
|
3012
|
+
const logConnecting = () => {
|
|
3013
|
+
printer_default.print(`Connecting to ${wsHost}`);
|
|
3014
|
+
logger.info("Connecting to remote management", { wsUrl });
|
|
3015
|
+
};
|
|
3016
|
+
while (!_stopRequested) {
|
|
3017
|
+
logConnecting();
|
|
3018
|
+
setRemoteManagementState({ status: RemoteManagementStatus.Connecting, errorMessage: "" });
|
|
3019
|
+
try {
|
|
3020
|
+
await handleWebSocketConnection(wsUrl, wsHost, token);
|
|
3021
|
+
} catch (error) {
|
|
3022
|
+
logger.warn("Remote management connection error", { error: String(error) });
|
|
2807
3023
|
}
|
|
3024
|
+
if (_stopRequested) break;
|
|
3025
|
+
printer_default.warn(`Remote management disconnected. Reconnecting in ${RECONNECT_SLEEP_MS / 1e3} seconds...`);
|
|
3026
|
+
logger.info("Reconnecting to remote management after disconnect");
|
|
3027
|
+
await sleep(RECONNECT_SLEEP_MS);
|
|
3028
|
+
}
|
|
3029
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
3030
|
+
logger.info("Remote management stopped.");
|
|
3031
|
+
return getRemoteManagementState();
|
|
3032
|
+
}
|
|
3033
|
+
async function handleWebSocketConnection(wsUrl, wsHost, token) {
|
|
3034
|
+
return new Promise((resolve) => {
|
|
2808
3035
|
const ws = new import_ws.default(wsUrl, {
|
|
2809
3036
|
headers: { Authorization: `Bearer ${token}` }
|
|
2810
3037
|
});
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
return;
|
|
2834
|
-
}
|
|
2835
|
-
const text = data.toString("utf8");
|
|
2836
|
-
const req = JSON.parse(text);
|
|
2837
|
-
const webSocketHandler = new WebSocketCommandHandler();
|
|
2838
|
-
await webSocketHandler.handle(ws, req);
|
|
2839
|
-
} catch (e) {
|
|
2840
|
-
logger.warn("Failed handling websocket message", { error: String(e) });
|
|
2841
|
-
}
|
|
2842
|
-
});
|
|
2843
|
-
ws.on("unexpected-response", (_req, res) => {
|
|
2844
|
-
if (res.statusCode === 401) {
|
|
2845
|
-
printer_default.error("Unauthorized. Please enter a valid token.");
|
|
2846
|
-
logger.error("Unauthorized (401) on remote management connect");
|
|
2847
|
-
stopRequested = true;
|
|
2848
|
-
ws.close();
|
|
2849
|
-
} else {
|
|
2850
|
-
printer_default.warn(`Unexpected HTTP response ${res.statusCode} from server. Retrying...`);
|
|
2851
|
-
logger.warn("Unexpected HTTP response on WebSocket connect", { statusCode: res.statusCode });
|
|
2852
|
-
ws.close();
|
|
3038
|
+
currentWs = ws;
|
|
3039
|
+
let heartbeat = null;
|
|
3040
|
+
let firstMessage = true;
|
|
3041
|
+
const cleanup = () => {
|
|
3042
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
3043
|
+
currentWs = null;
|
|
3044
|
+
resolve();
|
|
3045
|
+
};
|
|
3046
|
+
ws.once("open", () => {
|
|
3047
|
+
printer_default.success(`Connected to ${wsHost}`);
|
|
3048
|
+
heartbeat = setInterval(() => {
|
|
3049
|
+
if (ws.readyState === import_ws.default.OPEN) ws.ping();
|
|
3050
|
+
}, PING_INTERVAL_MS);
|
|
3051
|
+
});
|
|
3052
|
+
ws.on("ping", () => ws.pong());
|
|
3053
|
+
ws.on("message", async (data) => {
|
|
3054
|
+
try {
|
|
3055
|
+
if (firstMessage) {
|
|
3056
|
+
firstMessage = false;
|
|
3057
|
+
const ok = handleConnectionStatusMessage(data);
|
|
3058
|
+
if (!ok) ws.close();
|
|
3059
|
+
return;
|
|
2853
3060
|
}
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
}
|
|
3061
|
+
setRemoteManagementState({ status: RemoteManagementStatus.Running, errorMessage: "" });
|
|
3062
|
+
const req = JSON.parse(data.toString("utf8"));
|
|
3063
|
+
await new WebSocketCommandHandler().handle(ws, req);
|
|
3064
|
+
} catch (e) {
|
|
3065
|
+
logger.warn("Failed handling websocket message", { error: String(e) });
|
|
3066
|
+
}
|
|
3067
|
+
});
|
|
3068
|
+
ws.on("unexpected-response", (_, res) => {
|
|
3069
|
+
setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: `HTTP ${res.statusCode}` });
|
|
3070
|
+
if (res.statusCode === 401) {
|
|
3071
|
+
printer_default.error("Unauthorized. Please enter a valid token.");
|
|
3072
|
+
logger.error("Unauthorized (401) on remote management connect");
|
|
3073
|
+
} else {
|
|
3074
|
+
printer_default.warn(`Unexpected HTTP ${res.statusCode}. Retrying...`);
|
|
3075
|
+
logger.warn("Unexpected HTTP response", { statusCode: res.statusCode });
|
|
3076
|
+
}
|
|
3077
|
+
ws.close();
|
|
3078
|
+
});
|
|
3079
|
+
ws.on("close", (code, reason) => {
|
|
3080
|
+
setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: "" });
|
|
3081
|
+
logger.info("WebSocket closed", { code, reason: reason.toString() });
|
|
3082
|
+
printer_default.warn(`Disconnected (code: ${code}). Retrying...`);
|
|
3083
|
+
cleanup();
|
|
3084
|
+
});
|
|
3085
|
+
ws.on("error", (err) => {
|
|
3086
|
+
setRemoteManagementState({ status: RemoteManagementStatus.Error, errorMessage: err.message });
|
|
3087
|
+
logger.warn("WebSocket error", { error: err.message });
|
|
3088
|
+
printer_default.error(err);
|
|
3089
|
+
cleanup();
|
|
2867
3090
|
});
|
|
2868
|
-
|
|
3091
|
+
});
|
|
3092
|
+
}
|
|
3093
|
+
async function closeRemoteManagement(timeoutMs = 1e4) {
|
|
3094
|
+
_stopRequested = true;
|
|
3095
|
+
try {
|
|
3096
|
+
if (currentWs) {
|
|
3097
|
+
try {
|
|
3098
|
+
setRemoteManagementState({ status: RemoteManagementStatus.Disconnecting, errorMessage: "" });
|
|
3099
|
+
currentWs.close();
|
|
3100
|
+
} catch (e) {
|
|
3101
|
+
logger.warn("Error while closing current remote management websocket", { error: String(e) });
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
const start = Date.now();
|
|
3105
|
+
while (_remoteManagementState.status === "RUNNING") {
|
|
3106
|
+
if (Date.now() - start > timeoutMs) {
|
|
3107
|
+
logger.warn("Timed out waiting for remote management to stop");
|
|
3108
|
+
break;
|
|
3109
|
+
}
|
|
3110
|
+
await sleep(200);
|
|
3111
|
+
}
|
|
3112
|
+
} finally {
|
|
3113
|
+
currentWs = null;
|
|
3114
|
+
setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: "" });
|
|
3115
|
+
return getRemoteManagementState();
|
|
2869
3116
|
}
|
|
2870
|
-
|
|
2871
|
-
|
|
3117
|
+
}
|
|
3118
|
+
function getRemoteManagementState() {
|
|
3119
|
+
return _remoteManagementState;
|
|
3120
|
+
}
|
|
3121
|
+
function setRemoteManagementState(state, errorMessage) {
|
|
3122
|
+
_remoteManagementState = {
|
|
3123
|
+
status: state.status,
|
|
3124
|
+
errorMessage: errorMessage || ""
|
|
3125
|
+
};
|
|
2872
3126
|
}
|
|
2873
3127
|
|
|
2874
3128
|
// src/utils/parseArgs.ts
|
|
@@ -2962,6 +3216,57 @@ var TunnelData = {
|
|
|
2962
3216
|
var activeTui = null;
|
|
2963
3217
|
var disconnectState = null;
|
|
2964
3218
|
var updateDisconnectState = null;
|
|
3219
|
+
async function launchTui(finalConfig, urls, greet) {
|
|
3220
|
+
try {
|
|
3221
|
+
const { withFullScreen } = await import("fullscreen-ink");
|
|
3222
|
+
const { default: TunnelTui2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
|
|
3223
|
+
const React3 = await import("react");
|
|
3224
|
+
const isTTYEnabled = process.stdin.isTTY;
|
|
3225
|
+
const TunnelTuiWrapper = ({ finalConfig: finalConfig2, urls: urls2, greet: greet2 }) => {
|
|
3226
|
+
const [disconnectInfo, setDisconnectInfo] = React3.useState(null);
|
|
3227
|
+
React3.useEffect(() => {
|
|
3228
|
+
updateDisconnectState = setDisconnectInfo;
|
|
3229
|
+
return () => {
|
|
3230
|
+
updateDisconnectState = null;
|
|
3231
|
+
};
|
|
3232
|
+
}, []);
|
|
3233
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
3234
|
+
TunnelTui2,
|
|
3235
|
+
{
|
|
3236
|
+
urls: urls2 ?? [],
|
|
3237
|
+
greet: greet2 ?? "",
|
|
3238
|
+
tunnelConfig: finalConfig2,
|
|
3239
|
+
disconnectInfo
|
|
3240
|
+
}
|
|
3241
|
+
);
|
|
3242
|
+
};
|
|
3243
|
+
const tui = withFullScreen(
|
|
3244
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
3245
|
+
TunnelTuiWrapper,
|
|
3246
|
+
{
|
|
3247
|
+
finalConfig,
|
|
3248
|
+
urls,
|
|
3249
|
+
greet
|
|
3250
|
+
}
|
|
3251
|
+
)
|
|
3252
|
+
);
|
|
3253
|
+
activeTui = tui;
|
|
3254
|
+
if (isTTYEnabled) {
|
|
3255
|
+
try {
|
|
3256
|
+
await tui.start();
|
|
3257
|
+
await tui.waitUntilExit();
|
|
3258
|
+
} catch (e) {
|
|
3259
|
+
logger.warn("TUI error", e);
|
|
3260
|
+
} finally {
|
|
3261
|
+
activeTui = null;
|
|
3262
|
+
}
|
|
3263
|
+
} else {
|
|
3264
|
+
printer_default.warn("Unable to initiate the TUI: your terminal does not support the required input mode.");
|
|
3265
|
+
}
|
|
3266
|
+
} catch (e) {
|
|
3267
|
+
logger.warn("Failed to (re-)initiate TUI", e);
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
2965
3270
|
async function startCli(finalConfig, manager) {
|
|
2966
3271
|
await printer_default.ensureDeps();
|
|
2967
3272
|
const chalk = await loadChalk();
|
|
@@ -3017,64 +3322,61 @@ async function startCli(finalConfig, manager) {
|
|
|
3017
3322
|
logger.warn("Failed to wait for TUI exit", e);
|
|
3018
3323
|
} finally {
|
|
3019
3324
|
activeTui = null;
|
|
3020
|
-
|
|
3325
|
+
printer_default.warn(`Error in tunnel:`);
|
|
3326
|
+
messages?.forEach(function(m) {
|
|
3021
3327
|
printer_default.warn(m);
|
|
3022
3328
|
});
|
|
3023
|
-
|
|
3329
|
+
if (!finalConfig.autoReconnect) {
|
|
3330
|
+
process.exit(0);
|
|
3331
|
+
}
|
|
3024
3332
|
}
|
|
3025
3333
|
} else {
|
|
3026
|
-
messages
|
|
3334
|
+
messages?.forEach(function(m) {
|
|
3027
3335
|
printer_default.warn(m);
|
|
3028
3336
|
});
|
|
3029
|
-
|
|
3337
|
+
if (!finalConfig.autoReconnect) {
|
|
3338
|
+
process.exit(0);
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
if (finalConfig.autoReconnect) {
|
|
3342
|
+
printer_default.startSpinner("Reconnecting to Pinggy");
|
|
3030
3343
|
}
|
|
3031
3344
|
});
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
const { default: TunnelTui2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
|
|
3035
|
-
const React3 = await import("react");
|
|
3036
|
-
const isTTYEnabled = process.stdin.isTTY;
|
|
3037
|
-
const TunnelTuiWrapper = ({ finalConfig: finalConfig2, urls, greet }) => {
|
|
3038
|
-
const [disconnectInfo, setDisconnectInfo] = React3.useState(null);
|
|
3039
|
-
React3.useEffect(() => {
|
|
3040
|
-
updateDisconnectState = setDisconnectInfo;
|
|
3041
|
-
return () => {
|
|
3042
|
-
updateDisconnectState = null;
|
|
3043
|
-
};
|
|
3044
|
-
}, []);
|
|
3045
|
-
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
3046
|
-
TunnelTui2,
|
|
3047
|
-
{
|
|
3048
|
-
urls: urls ?? [],
|
|
3049
|
-
greet: greet ?? "",
|
|
3050
|
-
tunnelConfig: finalConfig2,
|
|
3051
|
-
disconnectInfo
|
|
3052
|
-
}
|
|
3053
|
-
);
|
|
3054
|
-
};
|
|
3055
|
-
const tui = withFullScreen(
|
|
3056
|
-
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
3057
|
-
TunnelTuiWrapper,
|
|
3058
|
-
{
|
|
3059
|
-
finalConfig,
|
|
3060
|
-
urls: TunnelData.urls,
|
|
3061
|
-
greet: TunnelData.greet
|
|
3062
|
-
}
|
|
3063
|
-
)
|
|
3064
|
-
);
|
|
3065
|
-
activeTui = tui;
|
|
3066
|
-
if (isTTYEnabled) {
|
|
3345
|
+
try {
|
|
3346
|
+
await manager2.registerStartListener(tunnel.tunnelid, async (tunnelId, urls) => {
|
|
3067
3347
|
try {
|
|
3068
|
-
|
|
3069
|
-
await tui.waitUntilExit();
|
|
3348
|
+
printer_default.stopSpinnerSuccess("Reconnected to Pinggy");
|
|
3070
3349
|
} catch (e) {
|
|
3071
|
-
logger.warn("TUI error", e);
|
|
3072
|
-
} finally {
|
|
3073
|
-
activeTui = null;
|
|
3074
3350
|
}
|
|
3075
|
-
|
|
3076
|
-
printer_default.
|
|
3077
|
-
|
|
3351
|
+
printer_default.success(chalk.bold("Tunnel re-established!"));
|
|
3352
|
+
printer_default.print(chalk.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
3353
|
+
TunnelData.urls = urls;
|
|
3354
|
+
TunnelData.greet = await manager2.getTunnelGreetMessage(tunnel.tunnelid);
|
|
3355
|
+
printer_default.info(chalk.cyanBright("Remote URLs:"));
|
|
3356
|
+
(TunnelData.urls ?? []).forEach(
|
|
3357
|
+
(url) => printer_default.print(" " + chalk.magentaBright(url))
|
|
3358
|
+
);
|
|
3359
|
+
printer_default.print(chalk.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
3360
|
+
if (TunnelData.greet?.includes("not authenticated")) {
|
|
3361
|
+
printer_default.warn(chalk.yellowBright(TunnelData.greet));
|
|
3362
|
+
} else if (TunnelData.greet?.includes("authenticated as")) {
|
|
3363
|
+
const emailMatch = /authenticated as (.+)/.exec(TunnelData.greet);
|
|
3364
|
+
if (emailMatch) {
|
|
3365
|
+
const email = emailMatch[1];
|
|
3366
|
+
printer_default.info(chalk.cyanBright("Authenticated as: " + email));
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
printer_default.print(chalk.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
3370
|
+
printer_default.print(chalk.gray("\nPress Ctrl+C to stop the tunnel.\n"));
|
|
3371
|
+
if (!finalConfig.NoTUI) {
|
|
3372
|
+
await launchTui(finalConfig, TunnelData.urls, TunnelData.greet);
|
|
3373
|
+
}
|
|
3374
|
+
});
|
|
3375
|
+
} catch (e) {
|
|
3376
|
+
logger.debug("Failed to register start listener", e);
|
|
3377
|
+
}
|
|
3378
|
+
if (!finalConfig.NoTUI) {
|
|
3379
|
+
await launchTui(finalConfig, TunnelData.urls, TunnelData.greet);
|
|
3078
3380
|
}
|
|
3079
3381
|
} catch (err) {
|
|
3080
3382
|
printer_default.stopSpinnerFail("Failed to connect");
|
|
@@ -3090,6 +3392,8 @@ var import_fs3 = require("fs");
|
|
|
3090
3392
|
init_logger();
|
|
3091
3393
|
async function main() {
|
|
3092
3394
|
try {
|
|
3395
|
+
await printer_default.ensureDeps();
|
|
3396
|
+
await loadChalk();
|
|
3093
3397
|
const { values, positionals, hasAnyArgs } = parseCliArgs(cliOptions);
|
|
3094
3398
|
configureLogger(values);
|
|
3095
3399
|
const manager = TunnelManager.getInstance();
|
|
@@ -3137,5 +3441,8 @@ if (entryFile && entryFile === currentFile) {
|
|
|
3137
3441
|
0 && (module.exports = {
|
|
3138
3442
|
TunnelManager,
|
|
3139
3443
|
TunnelOperations,
|
|
3140
|
-
|
|
3444
|
+
closeRemoteManagement,
|
|
3445
|
+
enablePackageLogging,
|
|
3446
|
+
getRemoteManagementState,
|
|
3447
|
+
initiateRemoteManagement
|
|
3141
3448
|
});
|