pinggy 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/chunk-65R2GMKQ.js +2101 -0
- package/dist/index.cjs +1798 -1349
- package/dist/index.d.cts +616 -0
- package/dist/index.d.ts +616 -0
- package/dist/index.js +24 -2
- package/dist/{main-K44C44NW.js → main-2QDG7PWL.js} +166 -1705
- package/package.json +2 -3
- package/.github/workflows/npm-publish-github-packages.yml +0 -34
- package/.github/workflows/publish-binaries.yml +0 -223
- package/Makefile +0 -4
- package/caxa_build.js +0 -24
- package/dist/chunk-T5ESYDJY.js +0 -121
- package/ent.plist +0 -14
- package/jest.config.js +0 -19
- package/src/_tests_/build_config.test.ts +0 -91
- package/src/cli/buildConfig.ts +0 -535
- package/src/cli/defaults.ts +0 -20
- package/src/cli/extendedOptions.ts +0 -153
- package/src/cli/help.ts +0 -43
- package/src/cli/options.ts +0 -50
- package/src/cli/starCli.ts +0 -229
- package/src/index.ts +0 -31
- package/src/logger.ts +0 -138
- package/src/main.ts +0 -87
- package/src/remote_management/handler.ts +0 -244
- package/src/remote_management/remoteManagement.ts +0 -226
- package/src/remote_management/remote_schema.ts +0 -176
- package/src/remote_management/websocket_handlers.ts +0 -180
- package/src/tui/blessed/TunnelTui.ts +0 -340
- package/src/tui/blessed/components/DisplayUpdaters.ts +0 -189
- package/src/tui/blessed/components/KeyBindings.ts +0 -236
- package/src/tui/blessed/components/Modals.ts +0 -302
- package/src/tui/blessed/components/UIComponents.ts +0 -306
- package/src/tui/blessed/components/index.ts +0 -4
- package/src/tui/blessed/config.ts +0 -53
- package/src/tui/blessed/headerFetcher.ts +0 -42
- package/src/tui/blessed/index.ts +0 -2
- package/src/tui/blessed/qrCodeGenerator.ts +0 -20
- package/src/tui/blessed/webDebuggerConnection.ts +0 -128
- package/src/tui/ink/asciArt.ts +0 -7
- package/src/tui/ink/hooks/useQrCodes.ts +0 -27
- package/src/tui/ink/hooks/useReqResHeaders.ts +0 -27
- package/src/tui/ink/hooks/useTerminalSize.ts +0 -26
- package/src/tui/ink/hooks/useTerminalStats.ts +0 -24
- package/src/tui/ink/hooks/useWebDebugger.ts +0 -98
- package/src/tui/ink/index.tsx +0 -243
- package/src/tui/ink/layout/Borders.tsx +0 -15
- package/src/tui/ink/layout/Container.tsx +0 -15
- package/src/tui/ink/sections/DebuggerDetailModal.tsx +0 -53
- package/src/tui/ink/sections/KeyBindings.tsx +0 -58
- package/src/tui/ink/sections/QrCodeSection.tsx +0 -28
- package/src/tui/ink/sections/StatsSection.tsx +0 -20
- package/src/tui/ink/sections/URLsSection.tsx +0 -53
- package/src/tui/ink/utils/utils.ts +0 -35
- package/src/tui/spinner/spinner.ts +0 -64
- package/src/tunnel_manager/TunnelManager.ts +0 -1212
- package/src/types.ts +0 -255
- package/src/utils/FileServer.ts +0 -112
- package/src/utils/detect_vc_redist_on_windows.ts +0 -111
- package/src/utils/getFreePort.ts +0 -41
- package/src/utils/htmlTemplates.ts +0 -146
- package/src/utils/parseArgs.ts +0 -79
- package/src/utils/printer.ts +0 -81
- package/src/utils/util.ts +0 -18
- package/src/workers/file_serve_worker.ts +0 -33
- package/tsconfig.json +0 -17
- package/tsup.config.ts +0 -12
package/dist/index.cjs
CHANGED
|
@@ -29,6 +29,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
29
29
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
30
|
mod
|
|
31
31
|
));
|
|
32
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
33
|
|
|
33
34
|
// node_modules/tsup/assets/cjs_shims.js
|
|
34
35
|
var getImportMetaUrl, importMetaUrl;
|
|
@@ -321,6 +322,10 @@ var init_TunnelManager = __esm({
|
|
|
321
322
|
this.tunnelDisconnectListeners = /* @__PURE__ */ new Map();
|
|
322
323
|
this.tunnelWorkerErrorListeners = /* @__PURE__ */ new Map();
|
|
323
324
|
this.tunnelStartListeners = /* @__PURE__ */ new Map();
|
|
325
|
+
this.tunnelWillReconnectListeners = /* @__PURE__ */ new Map();
|
|
326
|
+
this.tunnelReconnectingListeners = /* @__PURE__ */ new Map();
|
|
327
|
+
this.tunnelReconnectionCompletedListeners = /* @__PURE__ */ new Map();
|
|
328
|
+
this.tunnelReconnectionFailedListeners = /* @__PURE__ */ new Map();
|
|
324
329
|
}
|
|
325
330
|
static getInstance() {
|
|
326
331
|
if (!_TunnelManager.instance) {
|
|
@@ -403,6 +408,10 @@ var init_TunnelManager = __esm({
|
|
|
403
408
|
this.setupStatsCallback(params.tunnelid, managed);
|
|
404
409
|
this.setupErrorCallback(params.tunnelid, managed);
|
|
405
410
|
this.setupDisconnectCallback(params.tunnelid, managed);
|
|
411
|
+
this.setupWillReconnectCallback(params.tunnelid, managed);
|
|
412
|
+
this.setupReconnectingCallback(params.tunnelid, managed);
|
|
413
|
+
this.setupReconnectionCompletedCallback(params.tunnelid, managed);
|
|
414
|
+
this.setupReconnectionFailedCallback(params.tunnelid, managed);
|
|
406
415
|
this.setUpTunnelWorkerErrorCallback(params.tunnelid, managed);
|
|
407
416
|
this.tunnelsByTunnelId.set(params.tunnelid, managed);
|
|
408
417
|
this.tunnelsByConfigId.set(params.configid, managed);
|
|
@@ -505,6 +514,10 @@ var init_TunnelManager = __esm({
|
|
|
505
514
|
this.tunnelDisconnectListeners.delete(tunnelId);
|
|
506
515
|
this.tunnelWorkerErrorListeners.delete(tunnelId);
|
|
507
516
|
this.tunnelStartListeners.delete(tunnelId);
|
|
517
|
+
this.tunnelWillReconnectListeners.delete(tunnelId);
|
|
518
|
+
this.tunnelReconnectingListeners.delete(tunnelId);
|
|
519
|
+
this.tunnelReconnectionCompletedListeners.delete(tunnelId);
|
|
520
|
+
this.tunnelReconnectionFailedListeners.delete(tunnelId);
|
|
508
521
|
managed.serveWorker = null;
|
|
509
522
|
managed.warnings = managed.warnings ?? [];
|
|
510
523
|
managed.isStopped = true;
|
|
@@ -638,6 +651,10 @@ var init_TunnelManager = __esm({
|
|
|
638
651
|
this.tunnelDisconnectListeners.delete(managed.tunnelid);
|
|
639
652
|
this.tunnelWorkerErrorListeners.delete(managed.tunnelid);
|
|
640
653
|
this.tunnelStartListeners.delete(managed.tunnelid);
|
|
654
|
+
this.tunnelWillReconnectListeners.delete(managed.tunnelid);
|
|
655
|
+
this.tunnelReconnectingListeners.delete(managed.tunnelid);
|
|
656
|
+
this.tunnelReconnectionCompletedListeners.delete(managed.tunnelid);
|
|
657
|
+
this.tunnelReconnectionFailedListeners.delete(managed.tunnelid);
|
|
641
658
|
this.tunnelsByTunnelId.delete(managed.tunnelid);
|
|
642
659
|
this.tunnelsByConfigId.delete(managed.configid);
|
|
643
660
|
} catch (e) {
|
|
@@ -718,6 +735,10 @@ var init_TunnelManager = __esm({
|
|
|
718
735
|
this.tunnelDisconnectListeners.delete(tunnelid);
|
|
719
736
|
this.tunnelWorkerErrorListeners.delete(tunnelid);
|
|
720
737
|
this.tunnelStartListeners.delete(tunnelid);
|
|
738
|
+
this.tunnelWillReconnectListeners.delete(tunnelid);
|
|
739
|
+
this.tunnelReconnectingListeners.delete(tunnelid);
|
|
740
|
+
this.tunnelReconnectionCompletedListeners.delete(tunnelid);
|
|
741
|
+
this.tunnelReconnectionFailedListeners.delete(tunnelid);
|
|
721
742
|
const newTunnel = await this._createTunnelWithProcessedConfig({
|
|
722
743
|
configid: currentConfigId,
|
|
723
744
|
tunnelid,
|
|
@@ -974,6 +995,58 @@ var init_TunnelManager = __esm({
|
|
|
974
995
|
logger.info("Start listener registered for tunnel", { tunnelId, listenerId });
|
|
975
996
|
return listenerId;
|
|
976
997
|
}
|
|
998
|
+
async registerWillReconnectListener(tunnelId, listener) {
|
|
999
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
1000
|
+
if (!managed) {
|
|
1001
|
+
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
1002
|
+
}
|
|
1003
|
+
if (!this.tunnelWillReconnectListeners.has(tunnelId)) {
|
|
1004
|
+
this.tunnelWillReconnectListeners.set(tunnelId, /* @__PURE__ */ new Map());
|
|
1005
|
+
}
|
|
1006
|
+
const listenerId = getRandomId();
|
|
1007
|
+
this.tunnelWillReconnectListeners.get(tunnelId).set(listenerId, listener);
|
|
1008
|
+
logger.info("WillReconnect listener registered for tunnel", { tunnelId, listenerId });
|
|
1009
|
+
return listenerId;
|
|
1010
|
+
}
|
|
1011
|
+
async registerReconnectingListener(tunnelId, listener) {
|
|
1012
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
1013
|
+
if (!managed) {
|
|
1014
|
+
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
1015
|
+
}
|
|
1016
|
+
if (!this.tunnelReconnectingListeners.has(tunnelId)) {
|
|
1017
|
+
this.tunnelReconnectingListeners.set(tunnelId, /* @__PURE__ */ new Map());
|
|
1018
|
+
}
|
|
1019
|
+
const listenerId = getRandomId();
|
|
1020
|
+
this.tunnelReconnectingListeners.get(tunnelId).set(listenerId, listener);
|
|
1021
|
+
logger.info("Reconnecting listener registered for tunnel", { tunnelId, listenerId });
|
|
1022
|
+
return listenerId;
|
|
1023
|
+
}
|
|
1024
|
+
async registerReconnectionCompletedListener(tunnelId, listener) {
|
|
1025
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
1026
|
+
if (!managed) {
|
|
1027
|
+
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
1028
|
+
}
|
|
1029
|
+
if (!this.tunnelReconnectionCompletedListeners.has(tunnelId)) {
|
|
1030
|
+
this.tunnelReconnectionCompletedListeners.set(tunnelId, /* @__PURE__ */ new Map());
|
|
1031
|
+
}
|
|
1032
|
+
const listenerId = getRandomId();
|
|
1033
|
+
this.tunnelReconnectionCompletedListeners.get(tunnelId).set(listenerId, listener);
|
|
1034
|
+
logger.info("ReconnectionCompleted listener registered for tunnel", { tunnelId, listenerId });
|
|
1035
|
+
return listenerId;
|
|
1036
|
+
}
|
|
1037
|
+
async registerReconnectionFailedListener(tunnelId, listener) {
|
|
1038
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
1039
|
+
if (!managed) {
|
|
1040
|
+
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
1041
|
+
}
|
|
1042
|
+
if (!this.tunnelReconnectionFailedListeners.has(tunnelId)) {
|
|
1043
|
+
this.tunnelReconnectionFailedListeners.set(tunnelId, /* @__PURE__ */ new Map());
|
|
1044
|
+
}
|
|
1045
|
+
const listenerId = getRandomId();
|
|
1046
|
+
this.tunnelReconnectionFailedListeners.get(tunnelId).set(listenerId, listener);
|
|
1047
|
+
logger.info("ReconnectionFailed listener registered for tunnel", { tunnelId, listenerId });
|
|
1048
|
+
return listenerId;
|
|
1049
|
+
}
|
|
977
1050
|
/**
|
|
978
1051
|
* Removes a previously registered stats listener.
|
|
979
1052
|
*
|
|
@@ -1028,6 +1101,72 @@ var init_TunnelManager = __esm({
|
|
|
1028
1101
|
logger.warn("Attempted to deregister non-existent disconnect listener", { tunnelId, listenerId });
|
|
1029
1102
|
}
|
|
1030
1103
|
}
|
|
1104
|
+
deregisterWillReconnectListener(tunnelId, listenerId) {
|
|
1105
|
+
const listeners = this.tunnelWillReconnectListeners.get(tunnelId);
|
|
1106
|
+
if (!listeners) {
|
|
1107
|
+
logger.warn("No will-reconnect listeners found for tunnel", { tunnelId });
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
;
|
|
1111
|
+
const removed = listeners.delete(listenerId);
|
|
1112
|
+
if (removed) {
|
|
1113
|
+
logger.info("WillReconnect listener deregistered", { tunnelId, listenerId });
|
|
1114
|
+
if (listeners.size === 0) {
|
|
1115
|
+
this.tunnelWillReconnectListeners.delete(tunnelId);
|
|
1116
|
+
}
|
|
1117
|
+
} else {
|
|
1118
|
+
logger.warn("Attempted to deregister non-existent will-reconnect listener", { tunnelId, listenerId });
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
deregisterReconnectingListener(tunnelId, listenerId) {
|
|
1122
|
+
const listeners = this.tunnelReconnectingListeners.get(tunnelId);
|
|
1123
|
+
if (!listeners) {
|
|
1124
|
+
logger.warn("No reconnecting listeners found for tunnel", { tunnelId });
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
;
|
|
1128
|
+
const removed = listeners.delete(listenerId);
|
|
1129
|
+
if (removed) {
|
|
1130
|
+
logger.info("Reconnecting listener deregistered", { tunnelId, listenerId });
|
|
1131
|
+
if (listeners.size === 0) {
|
|
1132
|
+
this.tunnelReconnectingListeners.delete(tunnelId);
|
|
1133
|
+
}
|
|
1134
|
+
} else {
|
|
1135
|
+
logger.warn("Attempted to deregister non-existent reconnecting listener", { tunnelId, listenerId });
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
deregisterReconnectionCompletedListener(tunnelId, listenerId) {
|
|
1139
|
+
const listeners = this.tunnelReconnectionCompletedListeners.get(tunnelId);
|
|
1140
|
+
if (!listeners) {
|
|
1141
|
+
logger.warn("No reconnection completed listeners found for tunnel", { tunnelId });
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
const removed = listeners.delete(listenerId);
|
|
1145
|
+
if (removed) {
|
|
1146
|
+
logger.info("Reconnection completed listener deregistered", { tunnelId, listenerId });
|
|
1147
|
+
if (listeners.size === 0) {
|
|
1148
|
+
this.tunnelReconnectionCompletedListeners.delete(tunnelId);
|
|
1149
|
+
}
|
|
1150
|
+
} else {
|
|
1151
|
+
logger.warn("Attempted to deregister non-existent reconnection completed listener", { tunnelId, listenerId });
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
deregisterReconnectionFailedListener(tunnelId, listenerId) {
|
|
1155
|
+
const listeners = this.tunnelReconnectionFailedListeners.get(tunnelId);
|
|
1156
|
+
if (!listeners) {
|
|
1157
|
+
logger.warn("No reconnection failed listeners found for tunnel", { tunnelId });
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
const removed = listeners.delete(listenerId);
|
|
1161
|
+
if (removed) {
|
|
1162
|
+
logger.info("Reconnection failed listener deregistered", { tunnelId, listenerId });
|
|
1163
|
+
if (listeners.size === 0) {
|
|
1164
|
+
this.tunnelReconnectionFailedListeners.delete(tunnelId);
|
|
1165
|
+
}
|
|
1166
|
+
} else {
|
|
1167
|
+
logger.warn("Attempted to deregister non-existent reconnection failed listener", { tunnelId, listenerId });
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1031
1170
|
async getLocalserverTlsInfo(tunnelId) {
|
|
1032
1171
|
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
1033
1172
|
if (!managed) {
|
|
@@ -1106,17 +1245,6 @@ var init_TunnelManager = __esm({
|
|
|
1106
1245
|
managedTunnel.isStopped = true;
|
|
1107
1246
|
managedTunnel.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1108
1247
|
}
|
|
1109
|
-
if (managedTunnel && managedTunnel.autoReconnect) {
|
|
1110
|
-
logger.info("Auto-reconnecting tunnel", { tunnelId });
|
|
1111
|
-
setTimeout(async () => {
|
|
1112
|
-
try {
|
|
1113
|
-
await this.restartTunnel(tunnelId);
|
|
1114
|
-
logger.info("Tunnel auto-reconnected successfully", { tunnelId });
|
|
1115
|
-
} catch (e) {
|
|
1116
|
-
logger.error("Failed to auto-reconnect tunnel", { tunnelId, e });
|
|
1117
|
-
}
|
|
1118
|
-
}, 1e4);
|
|
1119
|
-
}
|
|
1120
1248
|
const listeners = this.tunnelDisconnectListeners.get(tunnelId);
|
|
1121
1249
|
if (!listeners) return;
|
|
1122
1250
|
for (const [id, listener] of listeners) {
|
|
@@ -1136,6 +1264,140 @@ var init_TunnelManager = __esm({
|
|
|
1136
1264
|
logger.warn("Failed to set up disconnect callback", { tunnelId, error });
|
|
1137
1265
|
}
|
|
1138
1266
|
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Called when the tunnel disconnects and the SDK is about to start reconnecting.
|
|
1269
|
+
* Notifies registered will-reconnect listeners.
|
|
1270
|
+
*/
|
|
1271
|
+
setupWillReconnectCallback(tunnelId, managed) {
|
|
1272
|
+
try {
|
|
1273
|
+
const callback = ({ error, messages }) => {
|
|
1274
|
+
try {
|
|
1275
|
+
logger.info("Tunnel will reconnect", { tunnelId, error, messages });
|
|
1276
|
+
const listeners = this.tunnelWillReconnectListeners.get(tunnelId);
|
|
1277
|
+
if (!listeners) return;
|
|
1278
|
+
for (const [id, listener] of listeners) {
|
|
1279
|
+
try {
|
|
1280
|
+
listener(tunnelId, error, messages);
|
|
1281
|
+
} catch (err) {
|
|
1282
|
+
logger.debug("Error in will-reconnect-listener callback", { listenerId: id, tunnelId, err });
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
} catch (e) {
|
|
1286
|
+
logger.warn("Error handling will-reconnect callback", { tunnelId, e });
|
|
1287
|
+
}
|
|
1288
|
+
};
|
|
1289
|
+
managed.instance.setWillReconnectCallback(callback);
|
|
1290
|
+
logger.debug("WillReconnect callback set up for tunnel", { tunnelId });
|
|
1291
|
+
} catch (error) {
|
|
1292
|
+
logger.warn("Failed to set up will-reconnect callback", { tunnelId, error });
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Called for each reconnection attempt with the current retry count.
|
|
1297
|
+
* Notifies registered reconnecting listeners.
|
|
1298
|
+
*/
|
|
1299
|
+
setupReconnectingCallback(tunnelId, managed) {
|
|
1300
|
+
try {
|
|
1301
|
+
const callback = ({ retryCnt }) => {
|
|
1302
|
+
try {
|
|
1303
|
+
logger.info("Tunnel reconnecting", { tunnelId, retryCnt });
|
|
1304
|
+
const listeners = this.tunnelReconnectingListeners.get(tunnelId);
|
|
1305
|
+
if (!listeners) return;
|
|
1306
|
+
for (const [id, listener] of listeners) {
|
|
1307
|
+
try {
|
|
1308
|
+
listener(tunnelId, retryCnt);
|
|
1309
|
+
} catch (err) {
|
|
1310
|
+
logger.debug("Error in reconnecting-listener callback", { listenerId: id, tunnelId, err });
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
} catch (e) {
|
|
1314
|
+
logger.warn("Error handling reconnecting callback", { tunnelId, e });
|
|
1315
|
+
}
|
|
1316
|
+
};
|
|
1317
|
+
managed.instance.setReconnectingCallback(callback);
|
|
1318
|
+
logger.debug("Reconnecting callback set up for tunnel", { tunnelId });
|
|
1319
|
+
} catch (error) {
|
|
1320
|
+
logger.warn("Failed to set up reconnecting callback", { tunnelId, error });
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* Called when reconnection succeeds. Updates tunnel state back to active,
|
|
1325
|
+
* and notifies registered reconnection-completed and start listeners with new URLs.
|
|
1326
|
+
*/
|
|
1327
|
+
setupReconnectionCompletedCallback(tunnelId, managed) {
|
|
1328
|
+
try {
|
|
1329
|
+
const callback = ({ urls }) => {
|
|
1330
|
+
try {
|
|
1331
|
+
logger.info("Tunnel reconnection completed", { tunnelId, urls });
|
|
1332
|
+
const managedTunnel = this.tunnelsByTunnelId.get(tunnelId);
|
|
1333
|
+
if (managedTunnel) {
|
|
1334
|
+
managedTunnel.isStopped = false;
|
|
1335
|
+
managedTunnel.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1336
|
+
managedTunnel.stoppedAt = null;
|
|
1337
|
+
}
|
|
1338
|
+
const listeners = this.tunnelReconnectionCompletedListeners.get(tunnelId);
|
|
1339
|
+
if (listeners) {
|
|
1340
|
+
for (const [id, listener] of listeners) {
|
|
1341
|
+
try {
|
|
1342
|
+
listener(tunnelId, urls);
|
|
1343
|
+
} catch (err) {
|
|
1344
|
+
logger.debug("Error in reconnection-completed-listener callback", { listenerId: id, tunnelId, err });
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
const startListeners = this.tunnelStartListeners.get(tunnelId);
|
|
1349
|
+
if (startListeners) {
|
|
1350
|
+
for (const [id, listener] of startListeners) {
|
|
1351
|
+
try {
|
|
1352
|
+
listener(tunnelId, urls);
|
|
1353
|
+
} catch (err) {
|
|
1354
|
+
logger.debug("Error in start-listener callback on reconnection", { listenerId: id, tunnelId, err });
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
} catch (e) {
|
|
1359
|
+
logger.warn("Error handling reconnection-completed callback", { tunnelId, e });
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
managed.instance.setReconnectionCompletedCallback(callback);
|
|
1363
|
+
logger.debug("ReconnectionCompleted callback set up for tunnel", { tunnelId });
|
|
1364
|
+
} catch (error) {
|
|
1365
|
+
logger.warn("Failed to set up reconnection-completed callback", { tunnelId, error });
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Called when all reconnection attempts are exhausted.
|
|
1370
|
+
* Marks the tunnel as stopped and notifies registered reconnection-failed listeners.
|
|
1371
|
+
*/
|
|
1372
|
+
setupReconnectionFailedCallback(tunnelId, managed) {
|
|
1373
|
+
try {
|
|
1374
|
+
const callback = ({ retryCnt }) => {
|
|
1375
|
+
try {
|
|
1376
|
+
logger.error("Tunnel reconnection failed", { tunnelId, retryCnt });
|
|
1377
|
+
const managedTunnel = this.tunnelsByTunnelId.get(tunnelId);
|
|
1378
|
+
if (managedTunnel) {
|
|
1379
|
+
managedTunnel.isStopped = true;
|
|
1380
|
+
managedTunnel.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1381
|
+
}
|
|
1382
|
+
const listeners = this.tunnelReconnectionFailedListeners.get(tunnelId);
|
|
1383
|
+
if (!listeners) return;
|
|
1384
|
+
for (const [id, listener] of listeners) {
|
|
1385
|
+
try {
|
|
1386
|
+
listener(tunnelId, retryCnt);
|
|
1387
|
+
} catch (err) {
|
|
1388
|
+
logger.debug("Error in reconnection-failed-listener callback", { listenerId: id, tunnelId, err });
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
} catch (e) {
|
|
1392
|
+
logger.warn("Error handling reconnection-failed callback", { tunnelId, e });
|
|
1393
|
+
}
|
|
1394
|
+
};
|
|
1395
|
+
managed.instance.setReconnectionFailedCallback(callback);
|
|
1396
|
+
logger.debug("ReconnectionFailed callback set up for tunnel", { tunnelId });
|
|
1397
|
+
} catch (error) {
|
|
1398
|
+
logger.warn("Failed to set up reconnection-failed callback", { tunnelId, error });
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1139
1401
|
setUpTunnelWorkerErrorCallback(tunnelId, managed) {
|
|
1140
1402
|
try {
|
|
1141
1403
|
const callback = (error) => {
|
|
@@ -1251,1439 +1513,1461 @@ var init_TunnelManager = __esm({
|
|
|
1251
1513
|
}
|
|
1252
1514
|
});
|
|
1253
1515
|
|
|
1254
|
-
// src/
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
"src/cli/options.ts"() {
|
|
1258
|
-
"use strict";
|
|
1259
|
-
init_cjs_shims();
|
|
1260
|
-
cliOptions = {
|
|
1261
|
-
// SSH-like options
|
|
1262
|
-
R: { type: "string", multiple: true, description: "Local port. Eg. -R0:localhost:3000 will forward tunnel connections to local port 3000." },
|
|
1263
|
-
L: { type: "string", multiple: true, description: "Web Debugger address. Eg. -L4300:localhost:4300 will start web debugger on port 4300." },
|
|
1264
|
-
o: { type: "string", multiple: true, description: "Options", hidden: true },
|
|
1265
|
-
"server-port": { type: "string", short: "p", description: "Pinggy server port. Default: 443" },
|
|
1266
|
-
v4: { type: "boolean", short: "4", description: "IPv4 only", hidden: true },
|
|
1267
|
-
v6: { type: "boolean", short: "6", description: "IPv6 only", hidden: true },
|
|
1268
|
-
// These options appear in the ssh command, but we ignore it in CLI
|
|
1269
|
-
t: { type: "boolean", description: "hidden", hidden: true },
|
|
1270
|
-
T: { type: "boolean", description: "hidden", hidden: true },
|
|
1271
|
-
n: { type: "boolean", description: "hidden", hidden: true },
|
|
1272
|
-
N: { type: "boolean", description: "hidden", hidden: true },
|
|
1273
|
-
// Better options
|
|
1274
|
-
type: { type: "string", description: "Type of the connection. Eg. --type tcp" },
|
|
1275
|
-
localport: { type: "string", short: "l", description: "Takes input as [protocol:][host:]port. Eg. --localport https://localhost:8000 OR -l 3000" },
|
|
1276
|
-
debugger: { type: "string", short: "d", description: "Port for web debugger. Eg. --debugger 4300 OR -d 4300" },
|
|
1277
|
-
token: { type: "string", description: "Token for authentication. Eg. --token TOKEN_VALUE" },
|
|
1278
|
-
// Logging options (CLI overrides env)
|
|
1279
|
-
loglevel: { type: "string", description: "Logging level: ERROR, INFO, DEBUG. Overrides PINGGY_LOG_LEVEL environment variable" },
|
|
1280
|
-
logfile: { type: "string", description: "Path to log file. Overrides PINGGY_LOG_FILE environment variable" },
|
|
1281
|
-
v: { type: "boolean", description: "Print logs to stdout for Cli. Overrides PINGGY_LOG_STDOUT environment variable" },
|
|
1282
|
-
vv: { type: "boolean", description: "Enable detailed logging for the Node.js SDK and Libpinggy, including both info and debug level logs." },
|
|
1283
|
-
vvv: { type: "boolean", description: "Enable all logs from Cli, SDK and internal components." },
|
|
1284
|
-
autoreconnect: { type: "string", short: "a", description: "Automatically reconnect tunnel on failure. Use -a (defaults to true), -a true, or -a false." },
|
|
1285
|
-
// Save and load config
|
|
1286
|
-
saveconf: { type: "string", description: "Create the configuration file based on the options provided here" },
|
|
1287
|
-
conf: { type: "string", description: "Use the configuration file as base. Other options will be used to override this file" },
|
|
1288
|
-
// File server
|
|
1289
|
-
serve: { type: "string", description: "Start a webserver to serve files from the specified path. Eg --serve /path/to/files" },
|
|
1290
|
-
// Remote Control
|
|
1291
|
-
"remote-management": { type: "string", description: "Enable remote management of tunnels with token. Eg. --remote-management API_KEY" },
|
|
1292
|
-
manage: { type: "string", description: "Provide a server address to manage tunnels. Eg --manage dashboard.pinggy.io" },
|
|
1293
|
-
notui: { type: "boolean", description: "Disable TUI in remote management mode" },
|
|
1294
|
-
// Misc
|
|
1295
|
-
version: { type: "boolean", description: "Print version" },
|
|
1296
|
-
// Help
|
|
1297
|
-
help: { type: "boolean", short: "h", description: "Show this help message" }
|
|
1298
|
-
};
|
|
1299
|
-
}
|
|
1300
|
-
});
|
|
1301
|
-
|
|
1302
|
-
// src/cli/help.ts
|
|
1303
|
-
function printHelpMessage() {
|
|
1304
|
-
console.log("\nPinggy CLI Tool - Create secure tunnels to your localhost.");
|
|
1305
|
-
console.log("\nUsage:");
|
|
1306
|
-
console.log(" pinggy [options] -l <port>\n");
|
|
1307
|
-
console.log("Options:");
|
|
1308
|
-
for (const [key, value] of Object.entries(cliOptions)) {
|
|
1309
|
-
if (value.hidden) continue;
|
|
1310
|
-
const short = "short" in value && value.short ? `-${value.short}, ` : " ";
|
|
1311
|
-
const optType = value.type === "boolean" ? "" : "<value>";
|
|
1312
|
-
console.log(` ${short}--${key.padEnd(17)} ${optType.padEnd(8)} ${value.description}`);
|
|
1313
|
-
}
|
|
1314
|
-
console.log("\nExtended options :");
|
|
1315
|
-
console.log(" x:https Enforce HTTPS only (redirect HTTP to HTTPS)");
|
|
1316
|
-
console.log(" x:noreverseproxy Disable built-in reverse-proxy header injection");
|
|
1317
|
-
console.log(" x:localservertls:host Connect to local HTTPS server with SNI");
|
|
1318
|
-
console.log(" x:passpreflight Pass CORS preflight requests unchanged");
|
|
1319
|
-
console.log(" a:Key:Val Add header");
|
|
1320
|
-
console.log(" u:Key:Val Update header");
|
|
1321
|
-
console.log(" r:Key Remove header");
|
|
1322
|
-
console.log(" b:user:pass Basic auth");
|
|
1323
|
-
console.log(" k:BEARER Bearer token");
|
|
1324
|
-
console.log(" w:192.168.1.0/24 IP whitelist (CIDR)");
|
|
1325
|
-
console.log("\nExamples (User-friendly):");
|
|
1326
|
-
console.log(" pinggy -l 3000 # HTTP(S) tunnel to localhost port 3000");
|
|
1327
|
-
console.log(" pinggy --type tcp -l 22 # TCP tunnel for SSH (port 22)");
|
|
1328
|
-
console.log(" pinggy -l 8080 -d 4300 # HTTP tunnel to port 8080 with debugger running at localhost:4300");
|
|
1329
|
-
console.log(" pinggy --token mytoken -l 3000 # Authenticated tunnel");
|
|
1330
|
-
console.log(" pinggy x:https x:xff -l https://localhost:8443 # HTTPS-only + XFF");
|
|
1331
|
-
console.log(" pinggy w:192.168.1.0/24 -l 8080 # IP whitelist restriction");
|
|
1332
|
-
console.log("\nExamples (SSH-style):");
|
|
1333
|
-
console.log(" pinggy -R0:localhost:3000 # Basic HTTP tunnel");
|
|
1334
|
-
console.log(" pinggy --type tcp -R0:localhost:22 # TCP tunnel for SSH");
|
|
1335
|
-
console.log(" pinggy -R0:localhost:8080 -L4300:localhost:4300 # HTTP tunnel with debugger");
|
|
1336
|
-
console.log(" pinggy tcp@ap.example.com -R0:localhost:22 # TCP tunnel to region\n");
|
|
1516
|
+
// src/types.ts
|
|
1517
|
+
function isErrorResponse(obj) {
|
|
1518
|
+
return typeof obj === "object" && obj !== null && "code" in obj && "message" in obj && typeof obj.message === "string" && Object.values(ErrorCode).includes(obj.code);
|
|
1337
1519
|
}
|
|
1338
|
-
|
|
1339
|
-
"
|
|
1340
|
-
|
|
1341
|
-
init_cjs_shims();
|
|
1342
|
-
init_options();
|
|
1343
|
-
}
|
|
1344
|
-
});
|
|
1345
|
-
|
|
1346
|
-
// src/cli/defaults.ts
|
|
1347
|
-
var defaultOptions;
|
|
1348
|
-
var init_defaults = __esm({
|
|
1349
|
-
"src/cli/defaults.ts"() {
|
|
1350
|
-
"use strict";
|
|
1351
|
-
init_cjs_shims();
|
|
1352
|
-
defaultOptions = {
|
|
1353
|
-
token: void 0,
|
|
1354
|
-
// No default token
|
|
1355
|
-
serverAddress: "a.pinggy.io",
|
|
1356
|
-
forwarding: "localhost:8000",
|
|
1357
|
-
webDebugger: "",
|
|
1358
|
-
ipWhitelist: [],
|
|
1359
|
-
basicAuth: [],
|
|
1360
|
-
bearerTokenAuth: [],
|
|
1361
|
-
headerModification: [],
|
|
1362
|
-
force: false,
|
|
1363
|
-
xForwardedFor: false,
|
|
1364
|
-
httpsOnly: false,
|
|
1365
|
-
originalRequestUrl: false,
|
|
1366
|
-
allowPreflight: false,
|
|
1367
|
-
reverseProxy: false,
|
|
1368
|
-
autoReconnect: true
|
|
1369
|
-
};
|
|
1370
|
-
}
|
|
1371
|
-
});
|
|
1372
|
-
|
|
1373
|
-
// src/cli/extendedOptions.ts
|
|
1374
|
-
function parseExtendedOptions(options, config) {
|
|
1375
|
-
if (!options) return;
|
|
1376
|
-
for (const opt of options) {
|
|
1377
|
-
const [key, value] = opt.replace(/^"|"$/g, "").split(/:(.+)/).filter(Boolean);
|
|
1378
|
-
switch (key) {
|
|
1379
|
-
case "x":
|
|
1380
|
-
switch (value) {
|
|
1381
|
-
case "https":
|
|
1382
|
-
case "httpsonly":
|
|
1383
|
-
config.httpsOnly = true;
|
|
1384
|
-
break;
|
|
1385
|
-
case "passpreflight":
|
|
1386
|
-
case "allowpreflight":
|
|
1387
|
-
config.allowPreflight = true;
|
|
1388
|
-
break;
|
|
1389
|
-
case "reverseproxy":
|
|
1390
|
-
config.reverseProxy = false;
|
|
1391
|
-
break;
|
|
1392
|
-
case "xff":
|
|
1393
|
-
config.xForwardedFor = true;
|
|
1394
|
-
break;
|
|
1395
|
-
case "fullurl":
|
|
1396
|
-
case "fullrequesturl":
|
|
1397
|
-
config.originalRequestUrl = true;
|
|
1398
|
-
break;
|
|
1399
|
-
default:
|
|
1400
|
-
printer_default.warn(`Unknown extended option "${key}"`);
|
|
1401
|
-
logger.warn(`Warning: Unknown extended option "${key}"`);
|
|
1402
|
-
break;
|
|
1403
|
-
}
|
|
1404
|
-
break;
|
|
1405
|
-
case "w":
|
|
1406
|
-
if (value) {
|
|
1407
|
-
const ips = value.split(",").map((ip) => ip.trim()).filter(Boolean);
|
|
1408
|
-
const invalidIps = ips.filter((ip) => !(isValidIpV4Cidr(ip) || isValidIpV6Cidr(ip)));
|
|
1409
|
-
if (invalidIps.length > 0) {
|
|
1410
|
-
printer_default.warn(`Invalid IP/CIDR(s) in whitelist: ${invalidIps.join(", ")}`);
|
|
1411
|
-
logger.warn(`Warning: Invalid IP/CIDR(s) in whitelist: ${invalidIps.join(", ")}`);
|
|
1412
|
-
}
|
|
1413
|
-
if (!(invalidIps.length > 0)) {
|
|
1414
|
-
config.ipWhitelist = ips;
|
|
1415
|
-
}
|
|
1416
|
-
} else {
|
|
1417
|
-
printer_default.warn(`Extended option "${opt}" for 'w' requires IP(s)`);
|
|
1418
|
-
logger.warn(`Warning: Extended option "${opt}" for 'w' requires IP(s)`);
|
|
1419
|
-
}
|
|
1420
|
-
break;
|
|
1421
|
-
case "k":
|
|
1422
|
-
if (!config.bearerTokenAuth) config.bearerTokenAuth = [];
|
|
1423
|
-
if (value) {
|
|
1424
|
-
config.bearerTokenAuth.push(value);
|
|
1425
|
-
} else {
|
|
1426
|
-
printer_default.warn(`Extended option "${opt}" for 'k' requires a value`);
|
|
1427
|
-
logger.warn(`Warning: Extended option "${opt}" for 'k' requires a value`);
|
|
1428
|
-
}
|
|
1429
|
-
break;
|
|
1430
|
-
case "b":
|
|
1431
|
-
if (value && value.includes(":")) {
|
|
1432
|
-
const [username, password] = value.split(/:(.+)/);
|
|
1433
|
-
if (!config.basicAuth) config.basicAuth = [];
|
|
1434
|
-
config.basicAuth.push({ username, password });
|
|
1435
|
-
} else {
|
|
1436
|
-
printer_default.warn(`Extended option "${opt}" for 'b' requires value in format username:password`);
|
|
1437
|
-
logger.warn(`Warning: Extended option "${opt}" for 'b' requires value in format username:password`);
|
|
1438
|
-
}
|
|
1439
|
-
break;
|
|
1440
|
-
case "a":
|
|
1441
|
-
if (value && value.includes(":")) {
|
|
1442
|
-
const [key2, val] = value.split(/:(.+)/);
|
|
1443
|
-
if (!config.headerModification) config.headerModification = [];
|
|
1444
|
-
config.headerModification.push({ type: "add", key: key2, value: [val] });
|
|
1445
|
-
} else {
|
|
1446
|
-
printer_default.warn(`Extended option "${opt}" for 'a' requires key:value`);
|
|
1447
|
-
logger.warn(`Warning: Extended option "${opt}" for 'a' requires key:value`);
|
|
1448
|
-
}
|
|
1449
|
-
break;
|
|
1450
|
-
case "u":
|
|
1451
|
-
if (value && value.includes(":")) {
|
|
1452
|
-
const [key2, val] = value.split(/:(.+)/);
|
|
1453
|
-
if (!config.headerModification) config.headerModification = [];
|
|
1454
|
-
config.headerModification.push({ type: "update", key: key2, value: [val] });
|
|
1455
|
-
} else {
|
|
1456
|
-
printer_default.warn(`Extended option "${opt}" for 'u' requires key:value`);
|
|
1457
|
-
logger.warn(`Warning: Extended option "${opt}" for 'u' requires key:value`);
|
|
1458
|
-
}
|
|
1459
|
-
break;
|
|
1460
|
-
case "r":
|
|
1461
|
-
if (value) {
|
|
1462
|
-
if (!config.headerModification) config.headerModification = [];
|
|
1463
|
-
config.headerModification.push({ type: "remove", key: value });
|
|
1464
|
-
} else {
|
|
1465
|
-
printer_default.warn(`Extended option "${opt}" for 'r' requires a key`);
|
|
1466
|
-
}
|
|
1467
|
-
break;
|
|
1468
|
-
default:
|
|
1469
|
-
printer_default.warn(`Unknown extended option "${key}"`);
|
|
1470
|
-
break;
|
|
1471
|
-
}
|
|
1520
|
+
function newErrorResponse(codeOrError, message) {
|
|
1521
|
+
if (typeof codeOrError === "object") {
|
|
1522
|
+
return codeOrError;
|
|
1472
1523
|
}
|
|
1524
|
+
return {
|
|
1525
|
+
code: codeOrError,
|
|
1526
|
+
message
|
|
1527
|
+
};
|
|
1473
1528
|
}
|
|
1474
|
-
function
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1529
|
+
function NewResponseObject(data) {
|
|
1530
|
+
const encoder = new TextEncoder();
|
|
1531
|
+
const bytes = encoder.encode(JSON.stringify(data));
|
|
1532
|
+
return {
|
|
1533
|
+
response: bytes,
|
|
1534
|
+
requestid: "",
|
|
1535
|
+
command: "",
|
|
1536
|
+
error: false,
|
|
1537
|
+
errorresponse: {}
|
|
1538
|
+
};
|
|
1484
1539
|
}
|
|
1485
|
-
function
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1540
|
+
function NewErrorResponseObject(errorResponse) {
|
|
1541
|
+
return {
|
|
1542
|
+
response: new Uint8Array(),
|
|
1543
|
+
requestid: "",
|
|
1544
|
+
command: "",
|
|
1545
|
+
error: true,
|
|
1546
|
+
errorresponse: errorResponse
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
function newStatus(tunnelState, errorCode, errorMsg) {
|
|
1550
|
+
let assignedState = tunnelState;
|
|
1551
|
+
if (tunnelState === "live" /* Live */) {
|
|
1552
|
+
assignedState = "running" /* Running */;
|
|
1553
|
+
} else if (tunnelState === "idle" /* New */) {
|
|
1554
|
+
assignedState = "idle" /* New */;
|
|
1555
|
+
} else if (tunnelState === "closed" /* Closed */) {
|
|
1556
|
+
assignedState = "exited" /* Exited */;
|
|
1494
1557
|
}
|
|
1495
|
-
|
|
1558
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1559
|
+
return {
|
|
1560
|
+
state: assignedState,
|
|
1561
|
+
errorcode: errorCode,
|
|
1562
|
+
errormsg: errorMsg,
|
|
1563
|
+
createdtimestamp: now,
|
|
1564
|
+
starttimestamp: now,
|
|
1565
|
+
endtimestamp: now,
|
|
1566
|
+
warnings: []
|
|
1567
|
+
};
|
|
1496
1568
|
}
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1569
|
+
function newStats() {
|
|
1570
|
+
return {
|
|
1571
|
+
numLiveConnections: 0,
|
|
1572
|
+
numTotalConnections: 0,
|
|
1573
|
+
numTotalReqBytes: 0,
|
|
1574
|
+
numTotalResBytes: 0,
|
|
1575
|
+
numTotalTxBytes: 0,
|
|
1576
|
+
elapsedTime: 0
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
var TunnelStateType, TunnelErrorCodeType, TunnelWarningCode, ErrorCode, RemoteManagementStatus;
|
|
1580
|
+
var init_types = __esm({
|
|
1581
|
+
"src/types.ts"() {
|
|
1500
1582
|
"use strict";
|
|
1501
1583
|
init_cjs_shims();
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1584
|
+
TunnelStateType = /* @__PURE__ */ ((TunnelStateType2) => {
|
|
1585
|
+
TunnelStateType2["New"] = "idle";
|
|
1586
|
+
TunnelStateType2["Starting"] = "starting";
|
|
1587
|
+
TunnelStateType2["Running"] = "running";
|
|
1588
|
+
TunnelStateType2["Live"] = "live";
|
|
1589
|
+
TunnelStateType2["Closed"] = "closed";
|
|
1590
|
+
TunnelStateType2["Exited"] = "exited";
|
|
1591
|
+
return TunnelStateType2;
|
|
1592
|
+
})(TunnelStateType || {});
|
|
1593
|
+
TunnelErrorCodeType = /* @__PURE__ */ ((TunnelErrorCodeType2) => {
|
|
1594
|
+
TunnelErrorCodeType2["NonResponsive"] = "non_responsive";
|
|
1595
|
+
TunnelErrorCodeType2["FailedToConnect"] = "failed_to_connect";
|
|
1596
|
+
TunnelErrorCodeType2["ErrorInAdditionalForwarding"] = "additional_forwarding_error";
|
|
1597
|
+
TunnelErrorCodeType2["WebdebuggerError"] = "webdebugger_error";
|
|
1598
|
+
TunnelErrorCodeType2["NoError"] = "";
|
|
1599
|
+
return TunnelErrorCodeType2;
|
|
1600
|
+
})(TunnelErrorCodeType || {});
|
|
1601
|
+
TunnelWarningCode = /* @__PURE__ */ ((TunnelWarningCode2) => {
|
|
1602
|
+
TunnelWarningCode2["InvalidTunnelServePath"] = "INVALID_TUNNEL_SERVE_PATH";
|
|
1603
|
+
TunnelWarningCode2["UnknownWarning"] = "UNKNOWN_WARNING";
|
|
1604
|
+
return TunnelWarningCode2;
|
|
1605
|
+
})(TunnelWarningCode || {});
|
|
1606
|
+
ErrorCode = {
|
|
1607
|
+
InvalidRequestMethodError: "INVALID_REQUEST_METHOD",
|
|
1608
|
+
InvalidRequestBodyError: "COULD_NOT_READ_BODY",
|
|
1609
|
+
InternalServerError: "INTERNAL_SERVER_ERROR",
|
|
1610
|
+
InvalidBodyFormatError: "INVALID_DATA_FORMAT",
|
|
1611
|
+
ErrorStartingTunnel: "ERROR_STARTING_TUNNEL",
|
|
1612
|
+
TunnelNotFound: "TUNNEL_WITH_ID_OR_CONFIG_ID_NOT_FOUND",
|
|
1613
|
+
TunnelAlreadyRunningError: "TUNNEL_WITH_ID_OR_CONFIG_ID_ALREADY_RUNNING",
|
|
1614
|
+
WebsocketUpgradeFailError: "WEBSOCKET_UPGRADE_FAILED",
|
|
1615
|
+
RemoteManagementAlreadyRunning: "REMOTE_MANAGEMENT_ALREADY_RUNNING",
|
|
1616
|
+
RemoteManagementNotRunning: "REMOTE_MANAGEMENT_NOT_RUNNING",
|
|
1617
|
+
RemoteManagementDeserializationFailed: "REMOTE_MANAGEMENT_DESERIALIZATION_FAILED"
|
|
1618
|
+
};
|
|
1619
|
+
RemoteManagementStatus = {
|
|
1620
|
+
Connecting: "CONNECTING",
|
|
1621
|
+
Disconnecting: "DISCONNECTING",
|
|
1622
|
+
Reconnecting: "RECONNECTING",
|
|
1623
|
+
Running: "RUNNING",
|
|
1624
|
+
NotRunning: "NOT_RUNNING",
|
|
1625
|
+
Error: "ERROR"
|
|
1626
|
+
};
|
|
1505
1627
|
}
|
|
1506
1628
|
});
|
|
1507
1629
|
|
|
1508
|
-
// src/
|
|
1509
|
-
function
|
|
1510
|
-
return
|
|
1630
|
+
// src/remote_management/remote_schema.ts
|
|
1631
|
+
function tunnelConfigToPinggyOptions(config) {
|
|
1632
|
+
return {
|
|
1633
|
+
token: config.token || "",
|
|
1634
|
+
serverAddress: config.serveraddress || "free.pinggy.io",
|
|
1635
|
+
forwarding: `${config.forwardedhost || "localhost"}:${config.localport}`,
|
|
1636
|
+
webDebugger: config.webdebuggerport ? `localhost:${config.webdebuggerport}` : "",
|
|
1637
|
+
ipWhitelist: config.ipwhitelist || [],
|
|
1638
|
+
basicAuth: config.basicauth ? config.basicauth : [],
|
|
1639
|
+
bearerTokenAuth: config.bearerauth ? [config.bearerauth] : [],
|
|
1640
|
+
headerModification: config.headermodification,
|
|
1641
|
+
xForwardedFor: !!config.xff,
|
|
1642
|
+
httpsOnly: config.httpsOnly,
|
|
1643
|
+
originalRequestUrl: config.fullRequestUrl,
|
|
1644
|
+
allowPreflight: config.allowPreflight,
|
|
1645
|
+
reverseProxy: config.noReverseProxy,
|
|
1646
|
+
force: config.force,
|
|
1647
|
+
autoReconnect: config.autoreconnect,
|
|
1648
|
+
optional: {
|
|
1649
|
+
sniServerName: config.localservertlssni || ""
|
|
1650
|
+
}
|
|
1651
|
+
};
|
|
1511
1652
|
}
|
|
1512
|
-
function
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1653
|
+
function pinggyOptionsToTunnelConfig(opts, configid, configName, localserverTls, greetMsg, additionalForwarding, serve) {
|
|
1654
|
+
const forwarding = Array.isArray(opts.forwarding) ? String(opts.forwarding[0].address).replace("//", "").replace(/\/$/, "") : String(opts.forwarding).replace("//", "").replace(/\/$/, "");
|
|
1655
|
+
const parsedForwardedHost = forwarding.split(":").length == 3 ? forwarding.split(":")[1] : forwarding.split(":")[0];
|
|
1656
|
+
const parsedLocalPort = forwarding.split(":").length == 3 ? parseInt(forwarding.split(":")[2], 10) : parseInt(forwarding.split(":")[1], 10);
|
|
1657
|
+
const tunnelType = (Array.isArray(opts.forwarding) ? opts.forwarding[0]?.type : void 0) ?? import_pinggy3.TunnelType.Http;
|
|
1658
|
+
const parsedTokens = opts.bearerTokenAuth ? Array.isArray(opts.bearerTokenAuth) ? opts.bearerTokenAuth : JSON.parse(opts.bearerTokenAuth) : [];
|
|
1659
|
+
return {
|
|
1660
|
+
allowPreflight: opts.allowPreflight ?? false,
|
|
1661
|
+
allowpreflight: opts.allowPreflight ?? false,
|
|
1662
|
+
autoreconnect: opts.autoReconnect ?? false,
|
|
1663
|
+
basicauth: opts.basicAuth && Object.keys(opts.basicAuth).length ? opts.basicAuth : null,
|
|
1664
|
+
bearerauth: parsedTokens.length ? parsedTokens.join(",") : null,
|
|
1665
|
+
configid,
|
|
1666
|
+
configname: configName,
|
|
1667
|
+
greetmsg: greetMsg || "",
|
|
1668
|
+
force: opts.force ?? false,
|
|
1669
|
+
forwardedhost: parsedForwardedHost || "localhost",
|
|
1670
|
+
fullRequestUrl: opts.originalRequestUrl ?? false,
|
|
1671
|
+
headermodification: opts.headerModification || [],
|
|
1672
|
+
//structured list
|
|
1673
|
+
httpsOnly: opts.httpsOnly ?? false,
|
|
1674
|
+
internalwebdebuggerport: 0,
|
|
1675
|
+
ipwhitelist: opts.ipWhitelist ? Array.isArray(opts.ipWhitelist) ? opts.ipWhitelist : JSON.parse(opts.ipWhitelist) : null,
|
|
1676
|
+
localport: parsedLocalPort || 0,
|
|
1677
|
+
localservertlssni: null,
|
|
1678
|
+
regioncode: "",
|
|
1679
|
+
noReverseProxy: opts.reverseProxy ?? false,
|
|
1680
|
+
serveraddress: opts.serverAddress || "free.pinggy.io",
|
|
1681
|
+
serverport: 0,
|
|
1682
|
+
statusCheckInterval: 0,
|
|
1683
|
+
token: opts.token || "",
|
|
1684
|
+
tunnelTimeout: 0,
|
|
1685
|
+
type: tunnelType,
|
|
1686
|
+
webdebuggerport: Number(opts.webDebugger?.split(":")[0]) || 0,
|
|
1687
|
+
xff: opts.xForwardedFor ? "1" : "",
|
|
1688
|
+
localsservertls: localserverTls || false,
|
|
1689
|
+
additionalForwarding: additionalForwarding || [],
|
|
1690
|
+
serve: serve || ""
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
var import_pinggy3, import_zod, HeaderModificationSchema, AdditionalForwardingSchema, TunnelConfigSchema, StartSchema, StopSchema, GetSchema, RestartSchema, UpdateConfigSchema;
|
|
1694
|
+
var init_remote_schema = __esm({
|
|
1695
|
+
"src/remote_management/remote_schema.ts"() {
|
|
1696
|
+
"use strict";
|
|
1697
|
+
init_cjs_shims();
|
|
1698
|
+
import_pinggy3 = require("@pinggy/pinggy");
|
|
1699
|
+
import_zod = require("zod");
|
|
1700
|
+
HeaderModificationSchema = import_zod.z.object({
|
|
1701
|
+
key: import_zod.z.string(),
|
|
1702
|
+
value: import_zod.z.array(import_zod.z.string()).optional(),
|
|
1703
|
+
type: import_zod.z.enum(["add", "remove", "update"])
|
|
1704
|
+
});
|
|
1705
|
+
AdditionalForwardingSchema = import_zod.z.object({
|
|
1706
|
+
remoteDomain: import_zod.z.string().optional(),
|
|
1707
|
+
remotePort: import_zod.z.number().optional(),
|
|
1708
|
+
localDomain: import_zod.z.string(),
|
|
1709
|
+
localPort: import_zod.z.number()
|
|
1710
|
+
});
|
|
1711
|
+
TunnelConfigSchema = import_zod.z.object({
|
|
1712
|
+
allowPreflight: import_zod.z.boolean().optional(),
|
|
1713
|
+
// primary key
|
|
1714
|
+
allowpreflight: import_zod.z.boolean().optional(),
|
|
1715
|
+
// legacy key
|
|
1716
|
+
autoreconnect: import_zod.z.boolean(),
|
|
1717
|
+
basicauth: import_zod.z.array(import_zod.z.object({ username: import_zod.z.string(), password: import_zod.z.string() })).nullable(),
|
|
1718
|
+
bearerauth: import_zod.z.string().nullable(),
|
|
1719
|
+
configid: import_zod.z.string(),
|
|
1720
|
+
configname: import_zod.z.string(),
|
|
1721
|
+
greetmsg: import_zod.z.string().optional(),
|
|
1722
|
+
force: import_zod.z.boolean(),
|
|
1723
|
+
forwardedhost: import_zod.z.string(),
|
|
1724
|
+
fullRequestUrl: import_zod.z.boolean(),
|
|
1725
|
+
headermodification: import_zod.z.array(HeaderModificationSchema),
|
|
1726
|
+
httpsOnly: import_zod.z.boolean(),
|
|
1727
|
+
internalwebdebuggerport: import_zod.z.number(),
|
|
1728
|
+
ipwhitelist: import_zod.z.array(import_zod.z.string()).nullable(),
|
|
1729
|
+
localport: import_zod.z.number(),
|
|
1730
|
+
localsservertls: import_zod.z.union([import_zod.z.boolean(), import_zod.z.string()]),
|
|
1731
|
+
localservertlssni: import_zod.z.string().nullable(),
|
|
1732
|
+
regioncode: import_zod.z.string(),
|
|
1733
|
+
noReverseProxy: import_zod.z.boolean(),
|
|
1734
|
+
serveraddress: import_zod.z.string(),
|
|
1735
|
+
serverport: import_zod.z.number(),
|
|
1736
|
+
statusCheckInterval: import_zod.z.number(),
|
|
1737
|
+
token: import_zod.z.string(),
|
|
1738
|
+
tunnelTimeout: import_zod.z.number(),
|
|
1739
|
+
type: import_zod.z.enum([
|
|
1740
|
+
import_pinggy3.TunnelType.Http,
|
|
1741
|
+
import_pinggy3.TunnelType.Tcp,
|
|
1742
|
+
import_pinggy3.TunnelType.Udp,
|
|
1743
|
+
import_pinggy3.TunnelType.Tls,
|
|
1744
|
+
import_pinggy3.TunnelType.TlsTcp
|
|
1745
|
+
]),
|
|
1746
|
+
webdebuggerport: import_zod.z.number(),
|
|
1747
|
+
xff: import_zod.z.string(),
|
|
1748
|
+
additionalForwarding: import_zod.z.array(AdditionalForwardingSchema).optional(),
|
|
1749
|
+
serve: import_zod.z.string().optional()
|
|
1750
|
+
}).superRefine((data, ctx) => {
|
|
1751
|
+
if (data.allowPreflight === void 0 && data.allowpreflight === void 0) {
|
|
1752
|
+
ctx.addIssue({
|
|
1753
|
+
code: "custom",
|
|
1754
|
+
message: "Either allowPreflight or allowpreflight is required",
|
|
1755
|
+
path: ["allowPreflight"]
|
|
1756
|
+
});
|
|
1536
1757
|
}
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1758
|
+
}).transform((data) => ({
|
|
1759
|
+
...data,
|
|
1760
|
+
allowPreflight: data.allowPreflight ?? data.allowpreflight,
|
|
1761
|
+
allowpreflight: data.allowPreflight ?? data.allowpreflight
|
|
1762
|
+
}));
|
|
1763
|
+
StartSchema = import_zod.z.object({
|
|
1764
|
+
tunnelID: import_zod.z.string().nullable().optional(),
|
|
1765
|
+
tunnelConfig: TunnelConfigSchema
|
|
1766
|
+
});
|
|
1767
|
+
StopSchema = import_zod.z.object({
|
|
1768
|
+
tunnelID: import_zod.z.string().min(1)
|
|
1769
|
+
});
|
|
1770
|
+
GetSchema = StopSchema;
|
|
1771
|
+
RestartSchema = StopSchema;
|
|
1772
|
+
UpdateConfigSchema = import_zod.z.object({
|
|
1773
|
+
tunnelConfig: TunnelConfigSchema
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
});
|
|
1777
|
+
|
|
1778
|
+
// src/remote_management/handler.ts
|
|
1779
|
+
var import_pinggy4, TunnelOperations;
|
|
1780
|
+
var init_handler = __esm({
|
|
1781
|
+
"src/remote_management/handler.ts"() {
|
|
1782
|
+
"use strict";
|
|
1783
|
+
init_cjs_shims();
|
|
1784
|
+
init_types();
|
|
1785
|
+
init_TunnelManager();
|
|
1786
|
+
init_remote_schema();
|
|
1787
|
+
import_pinggy4 = require("@pinggy/pinggy");
|
|
1788
|
+
TunnelOperations = class {
|
|
1789
|
+
constructor() {
|
|
1790
|
+
this.tunnelManager = TunnelManager.getInstance();
|
|
1791
|
+
}
|
|
1792
|
+
buildStatus(tunnelId, state, errorCode) {
|
|
1793
|
+
const status = newStatus(state, errorCode, "");
|
|
1794
|
+
try {
|
|
1795
|
+
const managed = this.tunnelManager.getManagedTunnel("", tunnelId);
|
|
1796
|
+
if (managed) {
|
|
1797
|
+
status.createdtimestamp = managed.createdAt || "";
|
|
1798
|
+
status.starttimestamp = managed.startedAt || "";
|
|
1799
|
+
status.endtimestamp = managed.stoppedAt || "";
|
|
1544
1800
|
}
|
|
1545
|
-
|
|
1801
|
+
} catch (e) {
|
|
1546
1802
|
}
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1803
|
+
return status;
|
|
1804
|
+
}
|
|
1805
|
+
// --- Helper to construct TunnelResponse ---
|
|
1806
|
+
async buildTunnelResponse(tunnelid, tunnelConfig, configid, tunnelName, additionalForwarding, serve) {
|
|
1807
|
+
const [status, stats, tlsInfo, greetMsg, remoteurls] = await Promise.all([
|
|
1808
|
+
this.tunnelManager.getTunnelStatus(tunnelid),
|
|
1809
|
+
this.tunnelManager.getLatestTunnelStats(tunnelid) || newStats(),
|
|
1810
|
+
this.tunnelManager.getLocalserverTlsInfo(tunnelid),
|
|
1811
|
+
this.tunnelManager.getTunnelGreetMessage(tunnelid),
|
|
1812
|
+
this.tunnelManager.getTunnelUrls(tunnelid)
|
|
1813
|
+
]);
|
|
1814
|
+
return {
|
|
1815
|
+
tunnelid,
|
|
1816
|
+
remoteurls,
|
|
1817
|
+
tunnelconfig: pinggyOptionsToTunnelConfig(tunnelConfig, configid, tunnelName, tlsInfo, greetMsg, additionalForwarding),
|
|
1818
|
+
status: this.buildStatus(tunnelid, status, "" /* NoError */),
|
|
1819
|
+
stats
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
error(code, err, fallback) {
|
|
1823
|
+
return newErrorResponse({
|
|
1824
|
+
code,
|
|
1825
|
+
message: err instanceof Error ? err.message : fallback
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
// --- Operations ---
|
|
1829
|
+
async handleStart(config) {
|
|
1830
|
+
try {
|
|
1831
|
+
const opts = tunnelConfigToPinggyOptions(config);
|
|
1832
|
+
const additionalForwardingParsed = config.additionalForwarding || [];
|
|
1833
|
+
const { tunnelid, instance, tunnelName, additionalForwarding, serve } = await this.tunnelManager.createTunnel({
|
|
1834
|
+
...opts,
|
|
1835
|
+
tunnelType: Array.isArray(config.type) ? config.type : config.type ? [config.type] : [import_pinggy4.TunnelType.Http],
|
|
1836
|
+
// Temporary fix in future we will not use this field.
|
|
1837
|
+
configid: config.configid,
|
|
1838
|
+
tunnelName: config.configname,
|
|
1839
|
+
additionalForwarding: additionalForwardingParsed,
|
|
1840
|
+
serve: config.serve
|
|
1841
|
+
});
|
|
1842
|
+
this.tunnelManager.startTunnel(tunnelid);
|
|
1843
|
+
const tunnelPconfig = await this.tunnelManager.getTunnelConfig("", tunnelid);
|
|
1844
|
+
const resp = this.buildTunnelResponse(tunnelid, tunnelPconfig, config.configid, tunnelName, additionalForwarding, serve);
|
|
1845
|
+
return resp;
|
|
1846
|
+
} catch (err) {
|
|
1847
|
+
return this.error(ErrorCode.ErrorStartingTunnel, err, "Unknown error occurred while starting tunnel");
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
async handleUpdateConfig(config) {
|
|
1851
|
+
try {
|
|
1852
|
+
const opts = tunnelConfigToPinggyOptions(config);
|
|
1853
|
+
const tunnel = await this.tunnelManager.updateConfig({
|
|
1854
|
+
...opts,
|
|
1855
|
+
tunnelType: Array.isArray(config.type) ? config.type : config.type ? [config.type] : [import_pinggy4.TunnelType.Http],
|
|
1856
|
+
// // Temporary fix in future we will not use this field.
|
|
1857
|
+
configid: config.configid,
|
|
1858
|
+
tunnelName: config.configname,
|
|
1859
|
+
additionalForwarding: config.additionalForwarding || [],
|
|
1860
|
+
serve: config.serve
|
|
1861
|
+
});
|
|
1862
|
+
if (!tunnel.instance || !tunnel.tunnelConfig)
|
|
1863
|
+
throw new Error("Invalid tunnel state after configuration update");
|
|
1864
|
+
return this.buildTunnelResponse(tunnel.tunnelid, tunnel.tunnelConfig, config.configid, tunnel.tunnelName, tunnel.additionalForwarding, tunnel.serve);
|
|
1865
|
+
} catch (err) {
|
|
1866
|
+
return this.error(ErrorCode.InternalServerError, err, "Failed to update tunnel configuration");
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
async handleList() {
|
|
1870
|
+
try {
|
|
1871
|
+
const tunnels = await this.tunnelManager.getAllTunnels();
|
|
1872
|
+
if (tunnels.length === 0) {
|
|
1873
|
+
return [];
|
|
1552
1874
|
}
|
|
1553
|
-
|
|
1875
|
+
return Promise.all(
|
|
1876
|
+
tunnels.map(async (t) => {
|
|
1877
|
+
const rawStats = this.tunnelManager.getLatestTunnelStats(t.tunnelid) || newStats();
|
|
1878
|
+
const [status, tlsInfo, greetMsg] = await Promise.all([
|
|
1879
|
+
this.tunnelManager.getTunnelStatus(t.tunnelid),
|
|
1880
|
+
this.tunnelManager.getLocalserverTlsInfo(t.tunnelid),
|
|
1881
|
+
this.tunnelManager.getTunnelGreetMessage(t.tunnelid)
|
|
1882
|
+
]);
|
|
1883
|
+
const pinggyOptions = status !== "closed" /* Closed */ && status !== "exited" /* Exited */ ? await this.tunnelManager.getTunnelConfig("", t.tunnelid) : t.tunnelConfig;
|
|
1884
|
+
const tunnelConfig = pinggyOptionsToTunnelConfig(pinggyOptions, t.configid, t.tunnelName, tlsInfo, greetMsg, t.additionalForwarding, t.serve);
|
|
1885
|
+
return {
|
|
1886
|
+
tunnelid: t.tunnelid,
|
|
1887
|
+
remoteurls: t.remoteurls,
|
|
1888
|
+
status: this.buildStatus(t.tunnelid, status, "" /* NoError */),
|
|
1889
|
+
stats: rawStats,
|
|
1890
|
+
tunnelconfig: tunnelConfig
|
|
1891
|
+
};
|
|
1892
|
+
})
|
|
1893
|
+
);
|
|
1894
|
+
} catch (err) {
|
|
1895
|
+
return this.error(ErrorCode.InternalServerError, err, "Failed to list tunnels");
|
|
1554
1896
|
}
|
|
1555
1897
|
}
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
let type;
|
|
1566
|
-
let forceFlag = false;
|
|
1567
|
-
let qrCode = false;
|
|
1568
|
-
let remaining = [...positionalArgs];
|
|
1569
|
-
if (typeof explicitToken === "string") {
|
|
1570
|
-
const parsed = parseUserAndDomain(explicitToken);
|
|
1571
|
-
if (parsed.server) server = parsed.server;
|
|
1572
|
-
if (parsed.type) type = parsed.type;
|
|
1573
|
-
if (parsed.token) token = parsed.token;
|
|
1574
|
-
if (parsed.forceFlag) forceFlag = true;
|
|
1575
|
-
if (parsed.qrCode) qrCode = true;
|
|
1576
|
-
}
|
|
1577
|
-
if (remaining.length > 0) {
|
|
1578
|
-
const first = remaining[0];
|
|
1579
|
-
const parsed = parseUserAndDomain(first);
|
|
1580
|
-
if (parsed.server) {
|
|
1581
|
-
server = parsed.server;
|
|
1582
|
-
if (parsed.type) type = parsed.type;
|
|
1583
|
-
if (parsed.token) token = parsed.token;
|
|
1584
|
-
if (parsed.forceFlag) forceFlag = true;
|
|
1585
|
-
if (parsed.qrCode) qrCode = true;
|
|
1586
|
-
remaining = remaining.slice(1);
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
return { token, server, type, forceFlag, qrCode, remaining };
|
|
1590
|
-
}
|
|
1591
|
-
function parseType(finalConfig, values, inferredType) {
|
|
1592
|
-
const t = inferredType || values.type || finalConfig.tunnelType;
|
|
1593
|
-
if (t === import_pinggy3.TunnelType.Http || t === import_pinggy3.TunnelType.Tcp || t === import_pinggy3.TunnelType.Tls || t === import_pinggy3.TunnelType.Udp || t === import_pinggy3.TunnelType.TlsTcp) {
|
|
1594
|
-
finalConfig.tunnelType = [t];
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
function parseLocalPort(finalConfig, values) {
|
|
1598
|
-
if (typeof values.localport !== "string") return null;
|
|
1599
|
-
let lp = values.localport.trim();
|
|
1600
|
-
let isHttps = false;
|
|
1601
|
-
if (lp.startsWith("https://")) {
|
|
1602
|
-
isHttps = true;
|
|
1603
|
-
lp = lp.replace(/^https:\/\//, "");
|
|
1604
|
-
} else if (lp.startsWith("http://")) {
|
|
1605
|
-
lp = lp.replace(/^http:\/\//, "");
|
|
1606
|
-
}
|
|
1607
|
-
const parts = lp.split(":");
|
|
1608
|
-
if (parts.length === 1) {
|
|
1609
|
-
const port = parseInt(parts[0], 10);
|
|
1610
|
-
if (!Number.isNaN(port) && isValidPort(port)) {
|
|
1611
|
-
finalConfig.forwarding = `localhost:${port}`;
|
|
1612
|
-
} else {
|
|
1613
|
-
return new Error("Invalid local port");
|
|
1614
|
-
}
|
|
1615
|
-
} else if (parts.length === 2) {
|
|
1616
|
-
const host = parts[0] || "localhost";
|
|
1617
|
-
const port = parseInt(parts[1], 10);
|
|
1618
|
-
if (!Number.isNaN(port) && isValidPort(port)) {
|
|
1619
|
-
finalConfig.forwarding = `${host}:${port}`;
|
|
1620
|
-
} else {
|
|
1621
|
-
return new Error("Invalid local port. Please use -h option for help.");
|
|
1622
|
-
}
|
|
1623
|
-
} else {
|
|
1624
|
-
return new Error("Invalid --localport format. Please use -h option for help.");
|
|
1625
|
-
}
|
|
1626
|
-
return null;
|
|
1627
|
-
}
|
|
1628
|
-
function removeIPv6Brackets(ip) {
|
|
1629
|
-
if (ip.startsWith("[") && ip.endsWith("]")) {
|
|
1630
|
-
return ip.slice(1, -1);
|
|
1631
|
-
}
|
|
1632
|
-
return ip;
|
|
1633
|
-
}
|
|
1634
|
-
function ipv6SafeSplitColon(s) {
|
|
1635
|
-
const result = [];
|
|
1636
|
-
let buf = "";
|
|
1637
|
-
const stack = [];
|
|
1638
|
-
for (let i = 0; i < s.length; i++) {
|
|
1639
|
-
const c = s[i];
|
|
1640
|
-
if (c === "[") {
|
|
1641
|
-
stack.push(c);
|
|
1642
|
-
} else if (c === "]" && stack.length > 0) {
|
|
1643
|
-
stack.pop();
|
|
1644
|
-
}
|
|
1645
|
-
if (c === ":" && stack.length === 0) {
|
|
1646
|
-
result.push(buf);
|
|
1647
|
-
buf = "";
|
|
1648
|
-
} else {
|
|
1649
|
-
buf += c;
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
result.push(buf);
|
|
1653
|
-
return result;
|
|
1654
|
-
}
|
|
1655
|
-
function parseDefaultForwarding(forwarding) {
|
|
1656
|
-
const parts = ipv6SafeSplitColon(forwarding);
|
|
1657
|
-
if (parts.length === 3) {
|
|
1658
|
-
const remotePort = parseInt(parts[0], 10);
|
|
1659
|
-
const localDomain = removeIPv6Brackets(parts[1] || "localhost");
|
|
1660
|
-
const localPort = parseInt(parts[2], 10);
|
|
1661
|
-
return { remotePort, localDomain, localPort };
|
|
1662
|
-
}
|
|
1663
|
-
if (parts.length === 4) {
|
|
1664
|
-
const remoteDomain = removeIPv6Brackets(parts[0]);
|
|
1665
|
-
const remotePort = parseInt(parts[1], 10);
|
|
1666
|
-
const localDomain = removeIPv6Brackets(parts[2] || "localhost");
|
|
1667
|
-
const localPort = parseInt(parts[3], 10);
|
|
1668
|
-
return { remoteDomain, remotePort, localDomain, localPort };
|
|
1669
|
-
}
|
|
1670
|
-
return new Error("forwarding address incorrect");
|
|
1671
|
-
}
|
|
1672
|
-
function parseAdditionalForwarding(forwarding) {
|
|
1673
|
-
const toPort = (v) => {
|
|
1674
|
-
if (!v) return null;
|
|
1675
|
-
const n = parseInt(v, 10);
|
|
1676
|
-
return Number.isNaN(n) ? null : n;
|
|
1677
|
-
};
|
|
1678
|
-
const parsed = ipv6SafeSplitColon(forwarding);
|
|
1679
|
-
if (parsed.length !== 4) {
|
|
1680
|
-
return new Error(
|
|
1681
|
-
"forwarding must be in format: [schema//]hostname[/port][@forwardingId]:<placeholder>:<forwardingAddress>:<forwardingPort>"
|
|
1682
|
-
);
|
|
1683
|
-
}
|
|
1684
|
-
const firstPart = parsed[0];
|
|
1685
|
-
const [hostPart] = firstPart.split("@");
|
|
1686
|
-
let protocol = "http";
|
|
1687
|
-
let remoteDomainRaw;
|
|
1688
|
-
let remotePort = 0;
|
|
1689
|
-
if (hostPart.includes("//")) {
|
|
1690
|
-
const [schema, rest] = hostPart.split("//");
|
|
1691
|
-
if (!schema || !VALID_PROTOCOLS.includes(schema)) {
|
|
1692
|
-
return new Error(`invalid protocol: ${schema}`);
|
|
1693
|
-
}
|
|
1694
|
-
protocol = schema;
|
|
1695
|
-
const domainAndPort = rest.split("/");
|
|
1696
|
-
if (domainAndPort.length > 2) {
|
|
1697
|
-
return new Error("invalid forwarding address format");
|
|
1698
|
-
}
|
|
1699
|
-
remoteDomainRaw = domainAndPort[0];
|
|
1700
|
-
if (!remoteDomainRaw || !domainRegex.test(remoteDomainRaw)) {
|
|
1701
|
-
return new Error("invalid remote domain");
|
|
1702
|
-
}
|
|
1703
|
-
const parsedRemotePort = toPort(domainAndPort[1]);
|
|
1704
|
-
if (protocol === "http") {
|
|
1705
|
-
remotePort = 0;
|
|
1706
|
-
} else {
|
|
1707
|
-
if (parsedRemotePort === null || !isValidPort(parsedRemotePort)) {
|
|
1708
|
-
return new Error(
|
|
1709
|
-
`${protocol} forwarding requires port in format ${protocol}//domain/remotePort`
|
|
1710
|
-
);
|
|
1898
|
+
async handleStop(tunnelid) {
|
|
1899
|
+
try {
|
|
1900
|
+
const { configid } = this.tunnelManager.stopTunnel(tunnelid);
|
|
1901
|
+
const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
|
|
1902
|
+
if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
|
|
1903
|
+
return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, configid, managed.tunnelName, managed.additionalForwarding, managed.serve);
|
|
1904
|
+
} catch (err) {
|
|
1905
|
+
return this.error(ErrorCode.TunnelNotFound, err, "Failed to stop tunnel");
|
|
1906
|
+
}
|
|
1711
1907
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
if (parts.length === 3) {
|
|
1767
|
-
const lp = parseInt(parts[0], 10);
|
|
1768
|
-
if (!Number.isNaN(lp) && isValidPort(lp)) {
|
|
1769
|
-
finalConfig.webDebugger = `localhost:${lp}`;
|
|
1770
|
-
} else {
|
|
1771
|
-
return new Error(`Invalid debugger port ${lp}`);
|
|
1772
|
-
}
|
|
1773
|
-
} else {
|
|
1774
|
-
return new Error("Incorrect command line arguments: web debugger address incorrect. Please use '-h' option for help.");
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
function parseDebugger(finalConfig, values) {
|
|
1778
|
-
let dbg = values.debugger;
|
|
1779
|
-
if (typeof dbg !== "string") return;
|
|
1780
|
-
dbg = dbg.startsWith(":") ? dbg.slice(1) : dbg;
|
|
1781
|
-
const d = parseInt(dbg, 10);
|
|
1782
|
-
if (!Number.isNaN(d) && isValidPort(d)) {
|
|
1783
|
-
finalConfig.webDebugger = `localhost:${d}`;
|
|
1784
|
-
} else {
|
|
1785
|
-
logger.error("Invalid debugger port:", dbg);
|
|
1786
|
-
return new Error(`Invalid debugger port ${dbg}. Please use '-h' option for help.`);
|
|
1787
|
-
}
|
|
1788
|
-
}
|
|
1789
|
-
function parseToken(finalConfig, explicitToken) {
|
|
1790
|
-
if (typeof explicitToken === "string" && explicitToken) {
|
|
1791
|
-
finalConfig.token = explicitToken;
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
function parseArgs(finalConfig, remainingPositionals) {
|
|
1795
|
-
parseExtendedOptions(remainingPositionals, finalConfig);
|
|
1796
|
-
}
|
|
1797
|
-
function storeJson(config, saveconf) {
|
|
1798
|
-
if (saveconf) {
|
|
1799
|
-
const path5 = saveconf;
|
|
1800
|
-
try {
|
|
1801
|
-
import_fs3.default.writeFileSync(path5, JSON.stringify(config, null, 2), { encoding: "utf-8", flag: "w" });
|
|
1802
|
-
logger.info(`Configuration saved to ${path5}`);
|
|
1803
|
-
} catch (err) {
|
|
1804
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1805
|
-
logger.error("Error loading configuration:", msg);
|
|
1806
|
-
}
|
|
1807
|
-
}
|
|
1808
|
-
}
|
|
1809
|
-
function loadJsonConfig(config) {
|
|
1810
|
-
const configpath = config["conf"];
|
|
1811
|
-
if (typeof configpath === "string" && configpath.trim().length > 0) {
|
|
1812
|
-
const filepath = import_path3.default.resolve(configpath);
|
|
1813
|
-
try {
|
|
1814
|
-
const data = import_fs3.default.readFileSync(filepath, { encoding: "utf-8" });
|
|
1815
|
-
const json = JSON.parse(data);
|
|
1816
|
-
return json;
|
|
1817
|
-
} catch (err) {
|
|
1818
|
-
logger.error("Error loading configuration:", err);
|
|
1819
|
-
}
|
|
1820
|
-
}
|
|
1821
|
-
return null;
|
|
1822
|
-
}
|
|
1823
|
-
function isSaveConfOption(values) {
|
|
1824
|
-
const saveconf = values["saveconf"];
|
|
1825
|
-
if (typeof saveconf === "string" && saveconf.trim().length > 0) {
|
|
1826
|
-
return saveconf;
|
|
1908
|
+
async handleGet(tunnelid) {
|
|
1909
|
+
try {
|
|
1910
|
+
const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
|
|
1911
|
+
if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
|
|
1912
|
+
return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, managed.configid, managed.tunnelName, managed.additionalForwarding, managed.serve);
|
|
1913
|
+
} catch (err) {
|
|
1914
|
+
return this.error(ErrorCode.TunnelNotFound, err, "Failed to get tunnel information");
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
async handleRestart(tunnelid) {
|
|
1918
|
+
try {
|
|
1919
|
+
await this.tunnelManager.restartTunnel(tunnelid);
|
|
1920
|
+
const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
|
|
1921
|
+
if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
|
|
1922
|
+
return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, managed.configid, managed.tunnelName, managed.additionalForwarding, managed.serve);
|
|
1923
|
+
} catch (err) {
|
|
1924
|
+
return this.error(ErrorCode.TunnelNotFound, err, "Failed to restart tunnel");
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
handleRegisterStatsListener(tunnelid, listener) {
|
|
1928
|
+
this.tunnelManager.registerStatsListener(tunnelid, listener);
|
|
1929
|
+
}
|
|
1930
|
+
handleUnregisterStatsListener(tunnelid, listnerId) {
|
|
1931
|
+
this.tunnelManager.deregisterStatsListener(tunnelid, listnerId);
|
|
1932
|
+
}
|
|
1933
|
+
handleGetTunnelStats(tunnelid) {
|
|
1934
|
+
try {
|
|
1935
|
+
const stats = this.tunnelManager.getTunnelStats(tunnelid);
|
|
1936
|
+
if (!stats) {
|
|
1937
|
+
return [newStats()];
|
|
1938
|
+
}
|
|
1939
|
+
return stats;
|
|
1940
|
+
} catch (err) {
|
|
1941
|
+
return this.error(ErrorCode.TunnelNotFound, err, "Failed to get tunnel stats");
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
handleRegisterDisconnectListener(tunnelid, listener) {
|
|
1945
|
+
this.tunnelManager.registerDisconnectListener(tunnelid, listener);
|
|
1946
|
+
}
|
|
1947
|
+
handleRemoveStoppedTunnelByConfigId(configId) {
|
|
1948
|
+
try {
|
|
1949
|
+
return this.tunnelManager.removeStoppedTunnelByConfigId(configId);
|
|
1950
|
+
} catch (err) {
|
|
1951
|
+
return this.error(ErrorCode.InternalServerError, err, "Failed to remove stopped tunnel by configId");
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
handleRemoveStoppedTunnelByTunnelId(tunnelId) {
|
|
1955
|
+
try {
|
|
1956
|
+
return this.tunnelManager.removeStoppedTunnelByTunnelId(tunnelId);
|
|
1957
|
+
} catch (err) {
|
|
1958
|
+
return this.error(ErrorCode.InternalServerError, err, "Failed to remove stopped tunnel by tunnelId");
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
};
|
|
1827
1962
|
}
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
if (trimmed === "true" || trimmed === "") {
|
|
1841
|
-
finalConfig.autoReconnect = true;
|
|
1842
|
-
} else if (trimmed === "false") {
|
|
1843
|
-
finalConfig.autoReconnect = false;
|
|
1844
|
-
} else {
|
|
1845
|
-
return new Error(`Invalid autoreconnect value: ${autoReconnectValue}. Use true or false.`);
|
|
1963
|
+
});
|
|
1964
|
+
|
|
1965
|
+
// src/remote_management/websocket_handlers.ts
|
|
1966
|
+
function handleConnectionStatusMessage(firstMessage) {
|
|
1967
|
+
try {
|
|
1968
|
+
const text = typeof firstMessage === "string" ? firstMessage : firstMessage.toString();
|
|
1969
|
+
const cs = JSON.parse(text);
|
|
1970
|
+
if (!cs.success) {
|
|
1971
|
+
const msg = cs.error_msg || "Connection failed";
|
|
1972
|
+
printer_default.warn(`Connection failed: ${msg}`);
|
|
1973
|
+
logger.warn("Remote management connection failed", { error_code: cs.error_code, error_msg: msg });
|
|
1974
|
+
return false;
|
|
1846
1975
|
}
|
|
1976
|
+
return true;
|
|
1977
|
+
} catch (e) {
|
|
1978
|
+
logger.warn("Failed to parse connection status message", { error: String(e) });
|
|
1979
|
+
return true;
|
|
1847
1980
|
}
|
|
1848
|
-
return null;
|
|
1849
|
-
}
|
|
1850
|
-
async function buildFinalConfig(values, positionals) {
|
|
1851
|
-
let token;
|
|
1852
|
-
let server;
|
|
1853
|
-
let type;
|
|
1854
|
-
let forceFlag = false;
|
|
1855
|
-
let qrCode = false;
|
|
1856
|
-
let finalConfig = new Object();
|
|
1857
|
-
let saveconf = isSaveConfOption(values);
|
|
1858
|
-
const configFromFile = loadJsonConfig(values);
|
|
1859
|
-
const userParse = parseUsers(positionals, values.token);
|
|
1860
|
-
token = userParse.token;
|
|
1861
|
-
server = userParse.server;
|
|
1862
|
-
type = userParse.type;
|
|
1863
|
-
forceFlag = userParse.forceFlag;
|
|
1864
|
-
qrCode = userParse.qrCode;
|
|
1865
|
-
const remainingPositionals = userParse.remaining;
|
|
1866
|
-
const initialTunnel = type || values.type;
|
|
1867
|
-
finalConfig = {
|
|
1868
|
-
...defaultOptions,
|
|
1869
|
-
...configFromFile || {},
|
|
1870
|
-
// Apply loaded config on top of defaults
|
|
1871
|
-
configid: getRandomId(),
|
|
1872
|
-
token: token || (configFromFile?.token || (typeof values.token === "string" ? values.token : "")),
|
|
1873
|
-
serverAddress: server || (configFromFile?.serverAddress || defaultOptions.serverAddress),
|
|
1874
|
-
tunnelType: initialTunnel ? [initialTunnel] : configFromFile?.tunnelType || [import_pinggy3.TunnelType.Http],
|
|
1875
|
-
NoTUI: values.notui || (configFromFile?.NoTUI || false),
|
|
1876
|
-
qrCode: qrCode || (configFromFile?.qrCode || false),
|
|
1877
|
-
autoReconnect: configFromFile?.autoReconnect ? configFromFile.autoReconnect : defaultOptions.autoReconnect
|
|
1878
|
-
};
|
|
1879
|
-
parseType(finalConfig, values, type);
|
|
1880
|
-
parseToken(finalConfig, token || values.token);
|
|
1881
|
-
const dbgErr = parseDebugger(finalConfig, values);
|
|
1882
|
-
if (dbgErr instanceof Error) throw dbgErr;
|
|
1883
|
-
const lpErr = parseLocalPort(finalConfig, values);
|
|
1884
|
-
if (lpErr instanceof Error) throw lpErr;
|
|
1885
|
-
const rErr = parseReverseTunnelAddr(finalConfig, values);
|
|
1886
|
-
if (rErr instanceof Error) throw rErr;
|
|
1887
|
-
const lErr = parseLocalTunnelAddr(finalConfig, values);
|
|
1888
|
-
if (lErr instanceof Error) throw lErr;
|
|
1889
|
-
const serveErr = parseServe(finalConfig, values);
|
|
1890
|
-
if (serveErr instanceof Error) throw serveErr;
|
|
1891
|
-
const autoReconnectErr = parseAutoReconnect(finalConfig, values);
|
|
1892
|
-
if (autoReconnectErr instanceof Error) throw autoReconnectErr;
|
|
1893
|
-
if (forceFlag) finalConfig.force = true;
|
|
1894
|
-
parseArgs(finalConfig, remainingPositionals);
|
|
1895
|
-
storeJson(finalConfig, saveconf);
|
|
1896
|
-
return finalConfig;
|
|
1897
1981
|
}
|
|
1898
|
-
var
|
|
1899
|
-
var
|
|
1900
|
-
"src/
|
|
1982
|
+
var import_zod2, WebSocketCommandHandler;
|
|
1983
|
+
var init_websocket_handlers = __esm({
|
|
1984
|
+
"src/remote_management/websocket_handlers.ts"() {
|
|
1901
1985
|
"use strict";
|
|
1902
1986
|
init_cjs_shims();
|
|
1903
|
-
init_defaults();
|
|
1904
|
-
init_extendedOptions();
|
|
1905
1987
|
init_logger();
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1988
|
+
init_types();
|
|
1989
|
+
init_handler();
|
|
1990
|
+
init_remote_schema();
|
|
1991
|
+
import_zod2 = __toESM(require("zod"), 1);
|
|
1992
|
+
init_printer();
|
|
1993
|
+
WebSocketCommandHandler = class {
|
|
1994
|
+
constructor() {
|
|
1995
|
+
this.tunnelHandler = new TunnelOperations();
|
|
1996
|
+
}
|
|
1997
|
+
safeParse(text) {
|
|
1998
|
+
if (!text) return void 0;
|
|
1999
|
+
try {
|
|
2000
|
+
return JSON.parse(text);
|
|
2001
|
+
} catch (e) {
|
|
2002
|
+
logger.warn("Invalid JSON payload", { error: String(e), text });
|
|
2003
|
+
return void 0;
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
sendResponse(ws, resp) {
|
|
2007
|
+
const payload = {
|
|
2008
|
+
...resp,
|
|
2009
|
+
response: Buffer.from(resp.response || []).toString("base64")
|
|
2010
|
+
};
|
|
2011
|
+
ws.send(JSON.stringify(payload));
|
|
2012
|
+
}
|
|
2013
|
+
sendError(ws, req, message, code = ErrorCode.InternalServerError) {
|
|
2014
|
+
const resp = NewErrorResponseObject({ code, message });
|
|
2015
|
+
resp.command = req.command || "";
|
|
2016
|
+
resp.requestid = req.requestid || "";
|
|
2017
|
+
this.sendResponse(ws, resp);
|
|
2018
|
+
}
|
|
2019
|
+
async handleStartReq(req, raw) {
|
|
2020
|
+
const dc = StartSchema.parse(raw);
|
|
2021
|
+
printer_default.info("Starting tunnel with config name: " + dc.tunnelConfig.configname);
|
|
2022
|
+
const result = await this.tunnelHandler.handleStart(dc.tunnelConfig);
|
|
2023
|
+
return this.wrapResponse(result, req);
|
|
2024
|
+
}
|
|
2025
|
+
async handleStopReq(req, raw) {
|
|
2026
|
+
const dc = StopSchema.parse(raw);
|
|
2027
|
+
printer_default.info("Stopping tunnel with ID: " + dc.tunnelID);
|
|
2028
|
+
const result = await this.tunnelHandler.handleStop(dc.tunnelID);
|
|
2029
|
+
return this.wrapResponse(result, req);
|
|
2030
|
+
}
|
|
2031
|
+
async handleGetReq(req, raw) {
|
|
2032
|
+
const dc = GetSchema.parse(raw);
|
|
2033
|
+
const result = await this.tunnelHandler.handleGet(dc.tunnelID);
|
|
2034
|
+
return this.wrapResponse(result, req);
|
|
2035
|
+
}
|
|
2036
|
+
async handleRestartReq(req, raw) {
|
|
2037
|
+
const dc = RestartSchema.parse(raw);
|
|
2038
|
+
const result = await this.tunnelHandler.handleRestart(dc.tunnelID);
|
|
2039
|
+
return this.wrapResponse(result, req);
|
|
2040
|
+
}
|
|
2041
|
+
async handleUpdateConfigReq(req, raw) {
|
|
2042
|
+
const dc = UpdateConfigSchema.parse(raw);
|
|
2043
|
+
const result = await this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig);
|
|
2044
|
+
return this.wrapResponse(result, req);
|
|
2045
|
+
}
|
|
2046
|
+
async handleListReq(req) {
|
|
2047
|
+
const result = await this.tunnelHandler.handleList();
|
|
2048
|
+
return this.wrapResponse(result, req);
|
|
2049
|
+
}
|
|
2050
|
+
wrapResponse(result, req) {
|
|
2051
|
+
if (isErrorResponse(result)) {
|
|
2052
|
+
const errResp = NewErrorResponseObject(result);
|
|
2053
|
+
errResp.command = req.command;
|
|
2054
|
+
errResp.requestid = req.requestid;
|
|
2055
|
+
return errResp;
|
|
2056
|
+
}
|
|
2057
|
+
const finalResult = JSON.parse(JSON.stringify(result));
|
|
2058
|
+
if (Array.isArray(finalResult)) {
|
|
2059
|
+
finalResult.forEach((item) => {
|
|
2060
|
+
if (item?.tunnelconfig) {
|
|
2061
|
+
delete item.tunnelconfig.allowPreflight;
|
|
2062
|
+
}
|
|
2063
|
+
});
|
|
2064
|
+
} else if (finalResult?.tunnelconfig) {
|
|
2065
|
+
delete finalResult.tunnelconfig.allowPreflight;
|
|
2066
|
+
}
|
|
2067
|
+
const respObj = NewResponseObject(finalResult);
|
|
2068
|
+
respObj.command = req.command;
|
|
2069
|
+
respObj.requestid = req.requestid;
|
|
2070
|
+
return respObj;
|
|
2071
|
+
}
|
|
2072
|
+
async handle(ws, req) {
|
|
2073
|
+
const cmd = (req.command || "").toLowerCase();
|
|
2074
|
+
const raw = this.safeParse(req.data);
|
|
2075
|
+
try {
|
|
2076
|
+
let response;
|
|
2077
|
+
switch (cmd) {
|
|
2078
|
+
case "start": {
|
|
2079
|
+
response = await this.handleStartReq(req, raw);
|
|
2080
|
+
break;
|
|
2081
|
+
}
|
|
2082
|
+
case "stop": {
|
|
2083
|
+
response = await this.handleStopReq(req, raw);
|
|
2084
|
+
break;
|
|
2085
|
+
}
|
|
2086
|
+
case "get": {
|
|
2087
|
+
response = await this.handleGetReq(req, raw);
|
|
2088
|
+
break;
|
|
2089
|
+
}
|
|
2090
|
+
case "restart": {
|
|
2091
|
+
response = await this.handleRestartReq(req, raw);
|
|
2092
|
+
break;
|
|
2093
|
+
}
|
|
2094
|
+
case "updateconfig": {
|
|
2095
|
+
response = await this.handleUpdateConfigReq(req, raw);
|
|
2096
|
+
break;
|
|
2097
|
+
}
|
|
2098
|
+
case "list": {
|
|
2099
|
+
response = await this.handleListReq(req);
|
|
2100
|
+
break;
|
|
2101
|
+
}
|
|
2102
|
+
default:
|
|
2103
|
+
if (typeof req.command === "string") {
|
|
2104
|
+
logger.warn("Unknown command", { command: req.command });
|
|
2105
|
+
}
|
|
2106
|
+
return this.sendError(ws, req, "Invalid command");
|
|
2107
|
+
}
|
|
2108
|
+
logger.debug("Sending response", { command: response.command, requestid: response.requestid });
|
|
2109
|
+
this.sendResponse(ws, response);
|
|
2110
|
+
} catch (e) {
|
|
2111
|
+
if (e instanceof import_zod2.default.ZodError) {
|
|
2112
|
+
logger.warn("Validation failed", { cmd, issues: e.issues });
|
|
2113
|
+
return this.sendError(ws, req, "Invalid request data", ErrorCode.InvalidBodyFormatError);
|
|
2114
|
+
}
|
|
2115
|
+
logger.error("Error handling command", { cmd, error: String(e) });
|
|
2116
|
+
return this.sendError(ws, req, e?.message || "Internal error");
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
};
|
|
1921
2120
|
}
|
|
1922
2121
|
});
|
|
1923
2122
|
|
|
1924
|
-
// src/
|
|
1925
|
-
function
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
if (typeof codeOrError === "object") {
|
|
1930
|
-
return codeOrError;
|
|
2123
|
+
// src/remote_management/remoteManagement.ts
|
|
2124
|
+
function buildRemoteManagementWsUrl(manage) {
|
|
2125
|
+
let baseUrl = (manage || "dashboard.pinggy.io").trim();
|
|
2126
|
+
if (!(baseUrl.startsWith("ws://") || baseUrl.startsWith("wss://"))) {
|
|
2127
|
+
baseUrl = "wss://" + baseUrl;
|
|
1931
2128
|
}
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
message
|
|
1935
|
-
};
|
|
2129
|
+
const trimmed = baseUrl.replace(/\/$/, "");
|
|
2130
|
+
return `${trimmed}/backend/api/v1/remote-management/connect`;
|
|
1936
2131
|
}
|
|
1937
|
-
function
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
error: false,
|
|
1945
|
-
errorresponse: {}
|
|
1946
|
-
};
|
|
2132
|
+
function extractHostname(u) {
|
|
2133
|
+
try {
|
|
2134
|
+
const url = new URL(u);
|
|
2135
|
+
return url.host;
|
|
2136
|
+
} catch {
|
|
2137
|
+
return u;
|
|
2138
|
+
}
|
|
1947
2139
|
}
|
|
1948
|
-
function
|
|
1949
|
-
return
|
|
1950
|
-
response: new Uint8Array(),
|
|
1951
|
-
requestid: "",
|
|
1952
|
-
command: "",
|
|
1953
|
-
error: true,
|
|
1954
|
-
errorresponse: errorResponse
|
|
1955
|
-
};
|
|
2140
|
+
function sleep(ms) {
|
|
2141
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
1956
2142
|
}
|
|
1957
|
-
function
|
|
1958
|
-
|
|
1959
|
-
if (
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
2143
|
+
async function parseRemoteManagement(values) {
|
|
2144
|
+
const rmToken = values["remote-management"];
|
|
2145
|
+
if (typeof rmToken === "string" && rmToken.trim().length > 0) {
|
|
2146
|
+
const manageHost = values["manage"];
|
|
2147
|
+
try {
|
|
2148
|
+
await initiateRemoteManagement(rmToken, manageHost);
|
|
2149
|
+
return { ok: true };
|
|
2150
|
+
} catch (e) {
|
|
2151
|
+
logger.error("Failed to initiate remote management:", e);
|
|
2152
|
+
return { ok: false, error: e };
|
|
2153
|
+
}
|
|
1965
2154
|
}
|
|
1966
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1967
|
-
return {
|
|
1968
|
-
state: assignedState,
|
|
1969
|
-
errorcode: errorCode,
|
|
1970
|
-
errormsg: errorMsg,
|
|
1971
|
-
createdtimestamp: now,
|
|
1972
|
-
starttimestamp: now,
|
|
1973
|
-
endtimestamp: now,
|
|
1974
|
-
warnings: []
|
|
1975
|
-
};
|
|
1976
|
-
}
|
|
1977
|
-
function newStats() {
|
|
1978
|
-
return {
|
|
1979
|
-
numLiveConnections: 0,
|
|
1980
|
-
numTotalConnections: 0,
|
|
1981
|
-
numTotalReqBytes: 0,
|
|
1982
|
-
numTotalResBytes: 0,
|
|
1983
|
-
numTotalTxBytes: 0,
|
|
1984
|
-
elapsedTime: 0
|
|
1985
|
-
};
|
|
1986
2155
|
}
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
"use strict";
|
|
1991
|
-
init_cjs_shims();
|
|
1992
|
-
ErrorCode = {
|
|
1993
|
-
InvalidRequestMethodError: "INVALID_REQUEST_METHOD",
|
|
1994
|
-
InvalidRequestBodyError: "COULD_NOT_READ_BODY",
|
|
1995
|
-
InternalServerError: "INTERNAL_SERVER_ERROR",
|
|
1996
|
-
InvalidBodyFormatError: "INVALID_DATA_FORMAT",
|
|
1997
|
-
ErrorStartingTunnel: "ERROR_STARTING_TUNNEL",
|
|
1998
|
-
TunnelNotFound: "TUNNEL_WITH_ID_OR_CONFIG_ID_NOT_FOUND",
|
|
1999
|
-
TunnelAlreadyRunningError: "TUNNEL_WITH_ID_OR_CONFIG_ID_ALREADY_RUNNING",
|
|
2000
|
-
WebsocketUpgradeFailError: "WEBSOCKET_UPGRADE_FAILED",
|
|
2001
|
-
RemoteManagementAlreadyRunning: "REMOTE_MANAGEMENT_ALREADY_RUNNING",
|
|
2002
|
-
RemoteManagementNotRunning: "REMOTE_MANAGEMENT_NOT_RUNNING",
|
|
2003
|
-
RemoteManagementDeserializationFailed: "REMOTE_MANAGEMENT_DESERIALIZATION_FAILED"
|
|
2004
|
-
};
|
|
2005
|
-
RemoteManagementStatus = {
|
|
2006
|
-
Connecting: "CONNECTING",
|
|
2007
|
-
Disconnecting: "DISCONNECTING",
|
|
2008
|
-
Reconnecting: "RECONNECTING",
|
|
2009
|
-
Running: "RUNNING",
|
|
2010
|
-
NotRunning: "NOT_RUNNING",
|
|
2011
|
-
Error: "ERROR"
|
|
2012
|
-
};
|
|
2156
|
+
async function initiateRemoteManagement(token, manage) {
|
|
2157
|
+
if (!token || token.trim().length === 0) {
|
|
2158
|
+
throw new Error("Remote management token is required (use --remote-management <TOKEN>)");
|
|
2013
2159
|
}
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
serverAddress: config.serveraddress || "free.pinggy.io",
|
|
2021
|
-
forwarding: `${config.forwardedhost || "localhost"}:${config.localport}`,
|
|
2022
|
-
webDebugger: config.webdebuggerport ? `localhost:${config.webdebuggerport}` : "",
|
|
2023
|
-
ipWhitelist: config.ipwhitelist || [],
|
|
2024
|
-
basicAuth: config.basicauth ? config.basicauth : [],
|
|
2025
|
-
bearerTokenAuth: config.bearerauth ? [config.bearerauth] : [],
|
|
2026
|
-
headerModification: config.headermodification,
|
|
2027
|
-
xForwardedFor: !!config.xff,
|
|
2028
|
-
httpsOnly: config.httpsOnly,
|
|
2029
|
-
originalRequestUrl: config.fullRequestUrl,
|
|
2030
|
-
allowPreflight: config.allowPreflight,
|
|
2031
|
-
reverseProxy: config.noReverseProxy,
|
|
2032
|
-
force: config.force,
|
|
2033
|
-
autoReconnect: config.autoreconnect,
|
|
2034
|
-
optional: {
|
|
2035
|
-
sniServerName: config.localservertlssni || ""
|
|
2036
|
-
}
|
|
2160
|
+
const wsUrl = buildRemoteManagementWsUrl(manage);
|
|
2161
|
+
const wsHost = extractHostname(wsUrl);
|
|
2162
|
+
logger.info("Remote management mode enabled.");
|
|
2163
|
+
_stopRequested = false;
|
|
2164
|
+
const sigintHandler = () => {
|
|
2165
|
+
_stopRequested = true;
|
|
2037
2166
|
};
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
const parsedLocalPort = forwarding.split(":").length == 3 ? parseInt(forwarding.split(":")[2], 10) : parseInt(forwarding.split(":")[1], 10);
|
|
2043
|
-
const tunnelType = (Array.isArray(opts.forwarding) ? opts.forwarding[0]?.type : void 0) ?? import_pinggy4.TunnelType.Http;
|
|
2044
|
-
const parsedTokens = opts.bearerTokenAuth ? Array.isArray(opts.bearerTokenAuth) ? opts.bearerTokenAuth : JSON.parse(opts.bearerTokenAuth) : [];
|
|
2045
|
-
return {
|
|
2046
|
-
allowPreflight: opts.allowPreflight ?? false,
|
|
2047
|
-
allowpreflight: opts.allowPreflight ?? false,
|
|
2048
|
-
autoreconnect: opts.autoReconnect ?? false,
|
|
2049
|
-
basicauth: opts.basicAuth && Object.keys(opts.basicAuth).length ? opts.basicAuth : null,
|
|
2050
|
-
bearerauth: parsedTokens.length ? parsedTokens.join(",") : null,
|
|
2051
|
-
configid,
|
|
2052
|
-
configname: configName,
|
|
2053
|
-
greetmsg: greetMsg || "",
|
|
2054
|
-
force: opts.force ?? false,
|
|
2055
|
-
forwardedhost: parsedForwardedHost || "localhost",
|
|
2056
|
-
fullRequestUrl: opts.originalRequestUrl ?? false,
|
|
2057
|
-
headermodification: opts.headerModification || [],
|
|
2058
|
-
//structured list
|
|
2059
|
-
httpsOnly: opts.httpsOnly ?? false,
|
|
2060
|
-
internalwebdebuggerport: 0,
|
|
2061
|
-
ipwhitelist: opts.ipWhitelist ? Array.isArray(opts.ipWhitelist) ? opts.ipWhitelist : JSON.parse(opts.ipWhitelist) : null,
|
|
2062
|
-
localport: parsedLocalPort || 0,
|
|
2063
|
-
localservertlssni: null,
|
|
2064
|
-
regioncode: "",
|
|
2065
|
-
noReverseProxy: opts.reverseProxy ?? false,
|
|
2066
|
-
serveraddress: opts.serverAddress || "free.pinggy.io",
|
|
2067
|
-
serverport: 0,
|
|
2068
|
-
statusCheckInterval: 0,
|
|
2069
|
-
token: opts.token || "",
|
|
2070
|
-
tunnelTimeout: 0,
|
|
2071
|
-
type: tunnelType,
|
|
2072
|
-
webdebuggerport: Number(opts.webDebugger?.split(":")[0]) || 0,
|
|
2073
|
-
xff: opts.xForwardedFor ? "1" : "",
|
|
2074
|
-
localsservertls: localserverTls || false,
|
|
2075
|
-
additionalForwarding: additionalForwarding || [],
|
|
2076
|
-
serve: serve || ""
|
|
2167
|
+
process.once("SIGINT", sigintHandler);
|
|
2168
|
+
const logConnecting = () => {
|
|
2169
|
+
printer_default.print(`Connecting to ${wsHost}`);
|
|
2170
|
+
logger.info("Connecting to remote management", { wsUrl });
|
|
2077
2171
|
};
|
|
2172
|
+
while (!_stopRequested) {
|
|
2173
|
+
logConnecting();
|
|
2174
|
+
setRemoteManagementState({ status: RemoteManagementStatus.Connecting, errorMessage: "" });
|
|
2175
|
+
try {
|
|
2176
|
+
await handleWebSocketConnection(wsUrl, wsHost, token);
|
|
2177
|
+
} catch (error) {
|
|
2178
|
+
logger.warn("Remote management connection error", { error: String(error) });
|
|
2179
|
+
}
|
|
2180
|
+
if (_stopRequested) break;
|
|
2181
|
+
printer_default.warn(`Remote management disconnected. Reconnecting in ${RECONNECT_SLEEP_MS / 1e3} seconds...`);
|
|
2182
|
+
logger.info("Reconnecting to remote management after disconnect");
|
|
2183
|
+
await sleep(RECONNECT_SLEEP_MS);
|
|
2184
|
+
}
|
|
2185
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
2186
|
+
logger.info("Remote management stopped.");
|
|
2187
|
+
return getRemoteManagementState();
|
|
2078
2188
|
}
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
init_cjs_shims();
|
|
2084
|
-
import_pinggy4 = require("@pinggy/pinggy");
|
|
2085
|
-
import_zod = require("zod");
|
|
2086
|
-
HeaderModificationSchema = import_zod.z.object({
|
|
2087
|
-
key: import_zod.z.string(),
|
|
2088
|
-
value: import_zod.z.array(import_zod.z.string()).optional(),
|
|
2089
|
-
type: import_zod.z.enum(["add", "remove", "update"])
|
|
2090
|
-
});
|
|
2091
|
-
AdditionalForwardingSchema = import_zod.z.object({
|
|
2092
|
-
remoteDomain: import_zod.z.string().optional(),
|
|
2093
|
-
remotePort: import_zod.z.number().optional(),
|
|
2094
|
-
localDomain: import_zod.z.string(),
|
|
2095
|
-
localPort: import_zod.z.number()
|
|
2189
|
+
async function handleWebSocketConnection(wsUrl, wsHost, token) {
|
|
2190
|
+
return new Promise((resolve) => {
|
|
2191
|
+
const ws = new import_ws.default(wsUrl, {
|
|
2192
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
2096
2193
|
});
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
type: import_zod.z.enum([
|
|
2126
|
-
import_pinggy4.TunnelType.Http,
|
|
2127
|
-
import_pinggy4.TunnelType.Tcp,
|
|
2128
|
-
import_pinggy4.TunnelType.Udp,
|
|
2129
|
-
import_pinggy4.TunnelType.Tls,
|
|
2130
|
-
import_pinggy4.TunnelType.TlsTcp
|
|
2131
|
-
]),
|
|
2132
|
-
webdebuggerport: import_zod.z.number(),
|
|
2133
|
-
xff: import_zod.z.string(),
|
|
2134
|
-
additionalForwarding: import_zod.z.array(AdditionalForwardingSchema).optional(),
|
|
2135
|
-
serve: import_zod.z.string().optional()
|
|
2136
|
-
}).superRefine((data, ctx) => {
|
|
2137
|
-
if (data.allowPreflight === void 0 && data.allowpreflight === void 0) {
|
|
2138
|
-
ctx.addIssue({
|
|
2139
|
-
code: "custom",
|
|
2140
|
-
message: "Either allowPreflight or allowpreflight is required",
|
|
2141
|
-
path: ["allowPreflight"]
|
|
2142
|
-
});
|
|
2194
|
+
currentWs = ws;
|
|
2195
|
+
let heartbeat = null;
|
|
2196
|
+
let firstMessage = true;
|
|
2197
|
+
const cleanup = () => {
|
|
2198
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
2199
|
+
currentWs = null;
|
|
2200
|
+
resolve();
|
|
2201
|
+
};
|
|
2202
|
+
ws.once("open", () => {
|
|
2203
|
+
printer_default.success(`Connected to ${wsHost}`);
|
|
2204
|
+
heartbeat = setInterval(() => {
|
|
2205
|
+
if (ws.readyState === import_ws.default.OPEN) ws.ping();
|
|
2206
|
+
}, PING_INTERVAL_MS);
|
|
2207
|
+
});
|
|
2208
|
+
ws.on("ping", () => ws.pong());
|
|
2209
|
+
ws.on("message", async (data) => {
|
|
2210
|
+
try {
|
|
2211
|
+
if (firstMessage) {
|
|
2212
|
+
firstMessage = false;
|
|
2213
|
+
const ok = handleConnectionStatusMessage(data);
|
|
2214
|
+
if (!ok) ws.close();
|
|
2215
|
+
return;
|
|
2216
|
+
}
|
|
2217
|
+
setRemoteManagementState({ status: RemoteManagementStatus.Running, errorMessage: "" });
|
|
2218
|
+
const req = JSON.parse(data.toString("utf8"));
|
|
2219
|
+
await new WebSocketCommandHandler().handle(ws, req);
|
|
2220
|
+
} catch (e) {
|
|
2221
|
+
logger.warn("Failed handling websocket message", { error: String(e) });
|
|
2143
2222
|
}
|
|
2144
|
-
}).transform((data) => ({
|
|
2145
|
-
...data,
|
|
2146
|
-
allowPreflight: data.allowPreflight ?? data.allowpreflight,
|
|
2147
|
-
allowpreflight: data.allowPreflight ?? data.allowpreflight
|
|
2148
|
-
}));
|
|
2149
|
-
StartSchema = import_zod.z.object({
|
|
2150
|
-
tunnelID: import_zod.z.string().nullable().optional(),
|
|
2151
|
-
tunnelConfig: TunnelConfigSchema
|
|
2152
2223
|
});
|
|
2153
|
-
|
|
2154
|
-
|
|
2224
|
+
ws.on("unexpected-response", (_, res) => {
|
|
2225
|
+
setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: `HTTP ${res.statusCode}` });
|
|
2226
|
+
if (res.statusCode === 401) {
|
|
2227
|
+
printer_default.error("Unauthorized. Please enter a valid token.");
|
|
2228
|
+
logger.error("Unauthorized (401) on remote management connect");
|
|
2229
|
+
} else {
|
|
2230
|
+
printer_default.warn(`Unexpected HTTP ${res.statusCode}. Retrying...`);
|
|
2231
|
+
logger.warn("Unexpected HTTP response", { statusCode: res.statusCode });
|
|
2232
|
+
}
|
|
2233
|
+
ws.close();
|
|
2155
2234
|
});
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2235
|
+
ws.on("close", (code, reason) => {
|
|
2236
|
+
setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: "" });
|
|
2237
|
+
logger.info("WebSocket closed", { code, reason: reason.toString() });
|
|
2238
|
+
printer_default.warn(`Disconnected (code: ${code}). Retrying...`);
|
|
2239
|
+
cleanup();
|
|
2240
|
+
});
|
|
2241
|
+
ws.on("error", (err) => {
|
|
2242
|
+
setRemoteManagementState({ status: RemoteManagementStatus.Error, errorMessage: err.message });
|
|
2243
|
+
logger.warn("WebSocket error", { error: err.message });
|
|
2244
|
+
printer_default.error(err);
|
|
2245
|
+
cleanup();
|
|
2160
2246
|
});
|
|
2247
|
+
});
|
|
2248
|
+
}
|
|
2249
|
+
async function closeRemoteManagement(timeoutMs = 1e4) {
|
|
2250
|
+
_stopRequested = true;
|
|
2251
|
+
try {
|
|
2252
|
+
if (currentWs) {
|
|
2253
|
+
try {
|
|
2254
|
+
setRemoteManagementState({ status: RemoteManagementStatus.Disconnecting, errorMessage: "" });
|
|
2255
|
+
currentWs.close();
|
|
2256
|
+
} catch (e) {
|
|
2257
|
+
logger.warn("Error while closing current remote management websocket", { error: String(e) });
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
const start = Date.now();
|
|
2261
|
+
while (_remoteManagementState.status === "RUNNING") {
|
|
2262
|
+
if (Date.now() - start > timeoutMs) {
|
|
2263
|
+
logger.warn("Timed out waiting for remote management to stop");
|
|
2264
|
+
break;
|
|
2265
|
+
}
|
|
2266
|
+
await sleep(200);
|
|
2267
|
+
}
|
|
2268
|
+
} finally {
|
|
2269
|
+
currentWs = null;
|
|
2270
|
+
setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: "" });
|
|
2271
|
+
return getRemoteManagementState();
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
function getRemoteManagementState() {
|
|
2275
|
+
return _remoteManagementState;
|
|
2276
|
+
}
|
|
2277
|
+
function setRemoteManagementState(state, errorMessage) {
|
|
2278
|
+
_remoteManagementState = {
|
|
2279
|
+
status: state.status,
|
|
2280
|
+
errorMessage: errorMessage || ""
|
|
2281
|
+
};
|
|
2282
|
+
}
|
|
2283
|
+
var import_ws, RECONNECT_SLEEP_MS, PING_INTERVAL_MS, _remoteManagementState, _stopRequested, currentWs;
|
|
2284
|
+
var init_remoteManagement = __esm({
|
|
2285
|
+
"src/remote_management/remoteManagement.ts"() {
|
|
2286
|
+
"use strict";
|
|
2287
|
+
init_cjs_shims();
|
|
2288
|
+
import_ws = __toESM(require("ws"), 1);
|
|
2289
|
+
init_logger();
|
|
2290
|
+
init_websocket_handlers();
|
|
2291
|
+
init_printer();
|
|
2292
|
+
init_types();
|
|
2293
|
+
RECONNECT_SLEEP_MS = 5e3;
|
|
2294
|
+
PING_INTERVAL_MS = 3e4;
|
|
2295
|
+
_remoteManagementState = {
|
|
2296
|
+
status: "NOT_RUNNING",
|
|
2297
|
+
errorMessage: ""
|
|
2298
|
+
};
|
|
2299
|
+
_stopRequested = false;
|
|
2300
|
+
currentWs = null;
|
|
2161
2301
|
}
|
|
2162
2302
|
});
|
|
2163
2303
|
|
|
2164
|
-
// src/
|
|
2165
|
-
var
|
|
2166
|
-
var
|
|
2167
|
-
"src/
|
|
2304
|
+
// src/cli/options.ts
|
|
2305
|
+
var cliOptions;
|
|
2306
|
+
var init_options = __esm({
|
|
2307
|
+
"src/cli/options.ts"() {
|
|
2168
2308
|
"use strict";
|
|
2169
2309
|
init_cjs_shims();
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
}
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
}
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
}
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2310
|
+
cliOptions = {
|
|
2311
|
+
// SSH-like options
|
|
2312
|
+
R: { type: "string", multiple: true, description: "Local port. Eg. -R0:localhost:3000 will forward tunnel connections to local port 3000." },
|
|
2313
|
+
L: { type: "string", multiple: true, description: "Web Debugger address. Eg. -L4300:localhost:4300 will start web debugger on port 4300." },
|
|
2314
|
+
o: { type: "string", multiple: true, description: "Options", hidden: true },
|
|
2315
|
+
"server-port": { type: "string", short: "p", description: "Pinggy server port. Default: 443" },
|
|
2316
|
+
v4: { type: "boolean", short: "4", description: "IPv4 only", hidden: true },
|
|
2317
|
+
v6: { type: "boolean", short: "6", description: "IPv6 only", hidden: true },
|
|
2318
|
+
// These options appear in the ssh command, but we ignore it in CLI
|
|
2319
|
+
t: { type: "boolean", description: "hidden", hidden: true },
|
|
2320
|
+
T: { type: "boolean", description: "hidden", hidden: true },
|
|
2321
|
+
n: { type: "boolean", description: "hidden", hidden: true },
|
|
2322
|
+
N: { type: "boolean", description: "hidden", hidden: true },
|
|
2323
|
+
// Better options
|
|
2324
|
+
type: { type: "string", description: "Type of the connection. Eg. --type tcp" },
|
|
2325
|
+
localport: { type: "string", short: "l", description: "Takes input as [protocol:][host:]port. Eg. --localport https://localhost:8000 OR -l 3000" },
|
|
2326
|
+
debugger: { type: "string", short: "d", description: "Port for web debugger. Eg. --debugger 4300 OR -d 4300" },
|
|
2327
|
+
token: { type: "string", description: "Token for authentication. Eg. --token TOKEN_VALUE" },
|
|
2328
|
+
// Logging options (CLI overrides env)
|
|
2329
|
+
loglevel: { type: "string", description: "Logging level: ERROR, INFO, DEBUG. Overrides PINGGY_LOG_LEVEL environment variable" },
|
|
2330
|
+
logfile: { type: "string", description: "Path to log file. Overrides PINGGY_LOG_FILE environment variable" },
|
|
2331
|
+
v: { type: "boolean", description: "Print logs to stdout for Cli. Overrides PINGGY_LOG_STDOUT environment variable" },
|
|
2332
|
+
vv: { type: "boolean", description: "Enable detailed logging for the Node.js SDK and Libpinggy, including both info and debug level logs." },
|
|
2333
|
+
vvv: { type: "boolean", description: "Enable all logs from Cli, SDK and internal components." },
|
|
2334
|
+
autoreconnect: { type: "string", short: "a", description: "Automatically reconnect tunnel on failure (enabled by default). Use -a false to disable." },
|
|
2335
|
+
// Save and load config
|
|
2336
|
+
saveconf: { type: "string", description: "Create the configuration file based on the options provided here" },
|
|
2337
|
+
conf: { type: "string", description: "Use the configuration file as base. Other options will be used to override this file" },
|
|
2338
|
+
// File server
|
|
2339
|
+
serve: { type: "string", description: "Start a webserver to serve files from the specified path. Eg --serve /path/to/files" },
|
|
2340
|
+
// Remote Control
|
|
2341
|
+
"remote-management": { type: "string", description: "Enable remote management of tunnels with token. Eg. --remote-management API_KEY" },
|
|
2342
|
+
manage: { type: "string", description: "Provide a server address to manage tunnels. Eg --manage dashboard.pinggy.io" },
|
|
2343
|
+
notui: { type: "boolean", description: "Disable TUI in remote management mode" },
|
|
2344
|
+
// Misc
|
|
2345
|
+
version: { type: "boolean", description: "Print version" },
|
|
2346
|
+
// Help
|
|
2347
|
+
help: { type: "boolean", short: "h", description: "Show this help message" }
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
});
|
|
2351
|
+
|
|
2352
|
+
// src/cli/help.ts
|
|
2353
|
+
function printHelpMessage() {
|
|
2354
|
+
console.log("\nPinggy CLI Tool - Create secure tunnels to your localhost.");
|
|
2355
|
+
console.log("\nUsage:");
|
|
2356
|
+
console.log(" pinggy [options] -l <port>\n");
|
|
2357
|
+
console.log("Options:");
|
|
2358
|
+
for (const [key, value] of Object.entries(cliOptions)) {
|
|
2359
|
+
if (value.hidden) continue;
|
|
2360
|
+
const short = "short" in value && value.short ? `-${value.short}, ` : " ";
|
|
2361
|
+
const optType = value.type === "boolean" ? "" : "<value>";
|
|
2362
|
+
console.log(` ${short}--${key.padEnd(17)} ${optType.padEnd(8)} ${value.description}`);
|
|
2363
|
+
}
|
|
2364
|
+
console.log("\nExtended options :");
|
|
2365
|
+
console.log(" x:https Enforce HTTPS only (redirect HTTP to HTTPS)");
|
|
2366
|
+
console.log(" x:noreverseproxy Disable built-in reverse-proxy header injection");
|
|
2367
|
+
console.log(" x:localservertls:host Connect to local HTTPS server with SNI");
|
|
2368
|
+
console.log(" x:passpreflight Pass CORS preflight requests unchanged");
|
|
2369
|
+
console.log(" a:Key:Val Add header");
|
|
2370
|
+
console.log(" u:Key:Val Update header");
|
|
2371
|
+
console.log(" r:Key Remove header");
|
|
2372
|
+
console.log(" b:user:pass Basic auth");
|
|
2373
|
+
console.log(" k:BEARER Bearer token");
|
|
2374
|
+
console.log(" w:192.168.1.0/24 IP whitelist (CIDR)");
|
|
2375
|
+
console.log("\nExamples (User-friendly):");
|
|
2376
|
+
console.log(" pinggy -l 3000 # HTTP(S) tunnel to localhost port 3000");
|
|
2377
|
+
console.log(" pinggy --type tcp -l 22 # TCP tunnel for SSH (port 22)");
|
|
2378
|
+
console.log(" pinggy -l 8080 -d 4300 # HTTP tunnel to port 8080 with debugger running at localhost:4300");
|
|
2379
|
+
console.log(" pinggy --token mytoken -l 3000 # Authenticated tunnel");
|
|
2380
|
+
console.log(" pinggy x:https x:xff -l https://localhost:8443 # HTTPS-only + XFF");
|
|
2381
|
+
console.log(" pinggy w:192.168.1.0/24 -l 8080 # IP whitelist restriction");
|
|
2382
|
+
console.log("\nExamples (SSH-style):");
|
|
2383
|
+
console.log(" pinggy -R0:localhost:3000 # Basic HTTP tunnel");
|
|
2384
|
+
console.log(" pinggy --type tcp -R0:localhost:22 # TCP tunnel for SSH");
|
|
2385
|
+
console.log(" pinggy -R0:localhost:8080 -L4300:localhost:4300 # HTTP tunnel with debugger");
|
|
2386
|
+
console.log(" pinggy tcp@ap.example.com -R0:localhost:22 # TCP tunnel to region\n");
|
|
2387
|
+
}
|
|
2388
|
+
var init_help = __esm({
|
|
2389
|
+
"src/cli/help.ts"() {
|
|
2390
|
+
"use strict";
|
|
2391
|
+
init_cjs_shims();
|
|
2392
|
+
init_options();
|
|
2393
|
+
}
|
|
2394
|
+
});
|
|
2395
|
+
|
|
2396
|
+
// src/cli/defaults.ts
|
|
2397
|
+
var defaultOptions;
|
|
2398
|
+
var init_defaults = __esm({
|
|
2399
|
+
"src/cli/defaults.ts"() {
|
|
2400
|
+
"use strict";
|
|
2401
|
+
init_cjs_shims();
|
|
2402
|
+
defaultOptions = {
|
|
2403
|
+
token: void 0,
|
|
2404
|
+
// No default token
|
|
2405
|
+
serverAddress: "a.pinggy.io",
|
|
2406
|
+
forwarding: "localhost:8000",
|
|
2407
|
+
webDebugger: "",
|
|
2408
|
+
ipWhitelist: [],
|
|
2409
|
+
basicAuth: [],
|
|
2410
|
+
bearerTokenAuth: [],
|
|
2411
|
+
headerModification: [],
|
|
2412
|
+
force: false,
|
|
2413
|
+
xForwardedFor: false,
|
|
2414
|
+
httpsOnly: false,
|
|
2415
|
+
originalRequestUrl: false,
|
|
2416
|
+
allowPreflight: false,
|
|
2417
|
+
reverseProxy: false,
|
|
2418
|
+
autoReconnect: true
|
|
2419
|
+
};
|
|
2420
|
+
}
|
|
2421
|
+
});
|
|
2422
|
+
|
|
2423
|
+
// src/cli/extendedOptions.ts
|
|
2424
|
+
function parseExtendedOptions(options, config) {
|
|
2425
|
+
if (!options) return;
|
|
2426
|
+
for (const opt of options) {
|
|
2427
|
+
const [key, value] = opt.replace(/^"|"$/g, "").split(/:(.+)/).filter(Boolean);
|
|
2428
|
+
switch (key) {
|
|
2429
|
+
case "x":
|
|
2430
|
+
switch (value) {
|
|
2431
|
+
case "https":
|
|
2432
|
+
case "httpsonly":
|
|
2433
|
+
config.httpsOnly = true;
|
|
2434
|
+
break;
|
|
2435
|
+
case "passpreflight":
|
|
2436
|
+
case "allowpreflight":
|
|
2437
|
+
config.allowPreflight = true;
|
|
2438
|
+
break;
|
|
2439
|
+
case "reverseproxy":
|
|
2440
|
+
config.reverseProxy = false;
|
|
2441
|
+
break;
|
|
2442
|
+
case "xff":
|
|
2443
|
+
config.xForwardedFor = true;
|
|
2444
|
+
break;
|
|
2445
|
+
case "fullurl":
|
|
2446
|
+
case "fullrequesturl":
|
|
2447
|
+
config.originalRequestUrl = true;
|
|
2448
|
+
break;
|
|
2449
|
+
default:
|
|
2450
|
+
printer_default.warn(`Unknown extended option "${key}"`);
|
|
2451
|
+
logger.warn(`Warning: Unknown extended option "${key}"`);
|
|
2452
|
+
break;
|
|
2253
2453
|
}
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
const
|
|
2258
|
-
|
|
2259
|
-
|
|
2454
|
+
break;
|
|
2455
|
+
case "w":
|
|
2456
|
+
if (value) {
|
|
2457
|
+
const ips = value.split(",").map((ip) => ip.trim()).filter(Boolean);
|
|
2458
|
+
const invalidIps = ips.filter((ip) => !(isValidIpV4Cidr(ip) || isValidIpV6Cidr(ip)));
|
|
2459
|
+
if (invalidIps.length > 0) {
|
|
2460
|
+
printer_default.warn(`Invalid IP/CIDR(s) in whitelist: ${invalidIps.join(", ")}`);
|
|
2461
|
+
logger.warn(`Warning: Invalid IP/CIDR(s) in whitelist: ${invalidIps.join(", ")}`);
|
|
2260
2462
|
}
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
this.tunnelManager.getTunnelGreetMessage(t.tunnelid)
|
|
2268
|
-
]);
|
|
2269
|
-
const pinggyOptions = status !== "closed" /* Closed */ && status !== "exited" /* Exited */ ? await this.tunnelManager.getTunnelConfig("", t.tunnelid) : t.tunnelConfig;
|
|
2270
|
-
const tunnelConfig = pinggyOptionsToTunnelConfig(pinggyOptions, t.configid, t.tunnelName, tlsInfo, greetMsg, t.additionalForwarding, t.serve);
|
|
2271
|
-
return {
|
|
2272
|
-
tunnelid: t.tunnelid,
|
|
2273
|
-
remoteurls: t.remoteurls,
|
|
2274
|
-
status: this.buildStatus(t.tunnelid, status, "" /* NoError */),
|
|
2275
|
-
stats: rawStats,
|
|
2276
|
-
tunnelconfig: tunnelConfig
|
|
2277
|
-
};
|
|
2278
|
-
})
|
|
2279
|
-
);
|
|
2280
|
-
} catch (err) {
|
|
2281
|
-
return this.error(ErrorCode.InternalServerError, err, "Failed to list tunnels");
|
|
2282
|
-
}
|
|
2283
|
-
}
|
|
2284
|
-
async handleStop(tunnelid) {
|
|
2285
|
-
try {
|
|
2286
|
-
const { configid } = this.tunnelManager.stopTunnel(tunnelid);
|
|
2287
|
-
const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
|
|
2288
|
-
if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
|
|
2289
|
-
return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, configid, managed.tunnelName, managed.additionalForwarding, managed.serve);
|
|
2290
|
-
} catch (err) {
|
|
2291
|
-
return this.error(ErrorCode.TunnelNotFound, err, "Failed to stop tunnel");
|
|
2463
|
+
if (!(invalidIps.length > 0)) {
|
|
2464
|
+
config.ipWhitelist = ips;
|
|
2465
|
+
}
|
|
2466
|
+
} else {
|
|
2467
|
+
printer_default.warn(`Extended option "${opt}" for 'w' requires IP(s)`);
|
|
2468
|
+
logger.warn(`Warning: Extended option "${opt}" for 'w' requires IP(s)`);
|
|
2292
2469
|
}
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2470
|
+
break;
|
|
2471
|
+
case "k":
|
|
2472
|
+
if (!config.bearerTokenAuth) config.bearerTokenAuth = [];
|
|
2473
|
+
if (value) {
|
|
2474
|
+
config.bearerTokenAuth.push(value);
|
|
2475
|
+
} else {
|
|
2476
|
+
printer_default.warn(`Extended option "${opt}" for 'k' requires a value`);
|
|
2477
|
+
logger.warn(`Warning: Extended option "${opt}" for 'k' requires a value`);
|
|
2301
2478
|
}
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2479
|
+
break;
|
|
2480
|
+
case "b":
|
|
2481
|
+
if (value && value.includes(":")) {
|
|
2482
|
+
const [username, password] = value.split(/:(.+)/);
|
|
2483
|
+
if (!config.basicAuth) config.basicAuth = [];
|
|
2484
|
+
config.basicAuth.push({ username, password });
|
|
2485
|
+
} else {
|
|
2486
|
+
printer_default.warn(`Extended option "${opt}" for 'b' requires value in format username:password`);
|
|
2487
|
+
logger.warn(`Warning: Extended option "${opt}" for 'b' requires value in format username:password`);
|
|
2311
2488
|
}
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
const stats = this.tunnelManager.getTunnelStats(tunnelid);
|
|
2322
|
-
if (!stats) {
|
|
2323
|
-
return [newStats()];
|
|
2324
|
-
}
|
|
2325
|
-
return stats;
|
|
2326
|
-
} catch (err) {
|
|
2327
|
-
return this.error(ErrorCode.TunnelNotFound, err, "Failed to get tunnel stats");
|
|
2489
|
+
break;
|
|
2490
|
+
case "a":
|
|
2491
|
+
if (value && value.includes(":")) {
|
|
2492
|
+
const [key2, val] = value.split(/:(.+)/);
|
|
2493
|
+
if (!config.headerModification) config.headerModification = [];
|
|
2494
|
+
config.headerModification.push({ type: "add", key: key2, value: [val] });
|
|
2495
|
+
} else {
|
|
2496
|
+
printer_default.warn(`Extended option "${opt}" for 'a' requires key:value`);
|
|
2497
|
+
logger.warn(`Warning: Extended option "${opt}" for 'a' requires key:value`);
|
|
2328
2498
|
}
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2499
|
+
break;
|
|
2500
|
+
case "u":
|
|
2501
|
+
if (value && value.includes(":")) {
|
|
2502
|
+
const [key2, val] = value.split(/:(.+)/);
|
|
2503
|
+
if (!config.headerModification) config.headerModification = [];
|
|
2504
|
+
config.headerModification.push({ type: "update", key: key2, value: [val] });
|
|
2505
|
+
} else {
|
|
2506
|
+
printer_default.warn(`Extended option "${opt}" for 'u' requires key:value`);
|
|
2507
|
+
logger.warn(`Warning: Extended option "${opt}" for 'u' requires key:value`);
|
|
2338
2508
|
}
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2509
|
+
break;
|
|
2510
|
+
case "r":
|
|
2511
|
+
if (value) {
|
|
2512
|
+
if (!config.headerModification) config.headerModification = [];
|
|
2513
|
+
config.headerModification.push({ type: "remove", key: value });
|
|
2514
|
+
} else {
|
|
2515
|
+
printer_default.warn(`Extended option "${opt}" for 'r' requires a key`);
|
|
2345
2516
|
}
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
// src/remote_management/websocket_handlers.ts
|
|
2352
|
-
function handleConnectionStatusMessage(firstMessage) {
|
|
2353
|
-
try {
|
|
2354
|
-
const text = typeof firstMessage === "string" ? firstMessage : firstMessage.toString();
|
|
2355
|
-
const cs = JSON.parse(text);
|
|
2356
|
-
if (!cs.success) {
|
|
2357
|
-
const msg = cs.error_msg || "Connection failed";
|
|
2358
|
-
printer_default.warn(`Connection failed: ${msg}`);
|
|
2359
|
-
logger.warn("Remote management connection failed", { error_code: cs.error_code, error_msg: msg });
|
|
2360
|
-
return false;
|
|
2517
|
+
break;
|
|
2518
|
+
default:
|
|
2519
|
+
printer_default.warn(`Unknown extended option "${key}"`);
|
|
2520
|
+
break;
|
|
2361
2521
|
}
|
|
2362
|
-
return true;
|
|
2363
|
-
} catch (e) {
|
|
2364
|
-
logger.warn("Failed to parse connection status message", { error: String(e) });
|
|
2365
|
-
return true;
|
|
2366
2522
|
}
|
|
2367
2523
|
}
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2524
|
+
function isValidIpV4Cidr(input) {
|
|
2525
|
+
if (input.includes("/")) {
|
|
2526
|
+
const [ip, mask] = input.split("/");
|
|
2527
|
+
if (!ip || !mask) return false;
|
|
2528
|
+
const isIp4 = (0, import_net.isIP)(ip) === 4;
|
|
2529
|
+
const maskNum = parseInt(mask, 10);
|
|
2530
|
+
const isMaskValid = !isNaN(maskNum) && maskNum >= 0 && maskNum <= 32;
|
|
2531
|
+
return isIp4 && isMaskValid;
|
|
2532
|
+
}
|
|
2533
|
+
return false;
|
|
2534
|
+
}
|
|
2535
|
+
function isValidIpV6Cidr(input) {
|
|
2536
|
+
if (input.includes("/")) {
|
|
2537
|
+
const [rawIp, mask] = input.split("/");
|
|
2538
|
+
if (!rawIp || !mask) return false;
|
|
2539
|
+
const ip = rawIp.split("%")[0].replace(/^\[|\]$/g, "");
|
|
2540
|
+
const isIp6 = (0, import_net.isIP)(ip) === 6;
|
|
2541
|
+
const maskNum = parseInt(mask, 10);
|
|
2542
|
+
const isMaskValid = !isNaN(maskNum) && maskNum >= 0 && maskNum <= 128;
|
|
2543
|
+
return isIp6 && isMaskValid;
|
|
2544
|
+
}
|
|
2545
|
+
return false;
|
|
2546
|
+
}
|
|
2547
|
+
var import_net;
|
|
2548
|
+
var init_extendedOptions = __esm({
|
|
2549
|
+
"src/cli/extendedOptions.ts"() {
|
|
2371
2550
|
"use strict";
|
|
2372
2551
|
init_cjs_shims();
|
|
2552
|
+
import_net = require("net");
|
|
2373
2553
|
init_logger();
|
|
2374
|
-
init_types();
|
|
2375
|
-
init_handler();
|
|
2376
|
-
init_remote_schema();
|
|
2377
|
-
import_zod2 = __toESM(require("zod"), 1);
|
|
2378
2554
|
init_printer();
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
this.sendResponse(ws, resp);
|
|
2404
|
-
}
|
|
2405
|
-
async handleStartReq(req, raw) {
|
|
2406
|
-
const dc = StartSchema.parse(raw);
|
|
2407
|
-
printer_default.info("Starting tunnel with config name: " + dc.tunnelConfig.configname);
|
|
2408
|
-
const result = await this.tunnelHandler.handleStart(dc.tunnelConfig);
|
|
2409
|
-
return this.wrapResponse(result, req);
|
|
2410
|
-
}
|
|
2411
|
-
async handleStopReq(req, raw) {
|
|
2412
|
-
const dc = StopSchema.parse(raw);
|
|
2413
|
-
printer_default.info("Stopping tunnel with ID: " + dc.tunnelID);
|
|
2414
|
-
const result = await this.tunnelHandler.handleStop(dc.tunnelID);
|
|
2415
|
-
return this.wrapResponse(result, req);
|
|
2416
|
-
}
|
|
2417
|
-
async handleGetReq(req, raw) {
|
|
2418
|
-
const dc = GetSchema.parse(raw);
|
|
2419
|
-
const result = await this.tunnelHandler.handleGet(dc.tunnelID);
|
|
2420
|
-
return this.wrapResponse(result, req);
|
|
2421
|
-
}
|
|
2422
|
-
async handleRestartReq(req, raw) {
|
|
2423
|
-
const dc = RestartSchema.parse(raw);
|
|
2424
|
-
const result = await this.tunnelHandler.handleRestart(dc.tunnelID);
|
|
2425
|
-
return this.wrapResponse(result, req);
|
|
2426
|
-
}
|
|
2427
|
-
async handleUpdateConfigReq(req, raw) {
|
|
2428
|
-
const dc = UpdateConfigSchema.parse(raw);
|
|
2429
|
-
const result = await this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig);
|
|
2430
|
-
return this.wrapResponse(result, req);
|
|
2431
|
-
}
|
|
2432
|
-
async handleListReq(req) {
|
|
2433
|
-
const result = await this.tunnelHandler.handleList();
|
|
2434
|
-
return this.wrapResponse(result, req);
|
|
2435
|
-
}
|
|
2436
|
-
wrapResponse(result, req) {
|
|
2437
|
-
if (isErrorResponse(result)) {
|
|
2438
|
-
const errResp = NewErrorResponseObject(result);
|
|
2439
|
-
errResp.command = req.command;
|
|
2440
|
-
errResp.requestid = req.requestid;
|
|
2441
|
-
return errResp;
|
|
2442
|
-
}
|
|
2443
|
-
const finalResult = JSON.parse(JSON.stringify(result));
|
|
2444
|
-
if (Array.isArray(finalResult)) {
|
|
2445
|
-
finalResult.forEach((item) => {
|
|
2446
|
-
if (item?.tunnelconfig) {
|
|
2447
|
-
delete item.tunnelconfig.allowPreflight;
|
|
2448
|
-
}
|
|
2449
|
-
});
|
|
2450
|
-
} else if (finalResult?.tunnelconfig) {
|
|
2451
|
-
delete finalResult.tunnelconfig.allowPreflight;
|
|
2555
|
+
}
|
|
2556
|
+
});
|
|
2557
|
+
|
|
2558
|
+
// src/cli/buildConfig.ts
|
|
2559
|
+
function isKeyword(str) {
|
|
2560
|
+
return KEYWORDS.has(str.toLowerCase());
|
|
2561
|
+
}
|
|
2562
|
+
function parseUserAndDomain(str) {
|
|
2563
|
+
let token;
|
|
2564
|
+
let type;
|
|
2565
|
+
let server;
|
|
2566
|
+
let qrCode;
|
|
2567
|
+
let forceFlag;
|
|
2568
|
+
if (!str) return { token, type, server, qrCode, forceFlag };
|
|
2569
|
+
if (str.includes("@")) {
|
|
2570
|
+
const [user, domain] = str.split("@", 2);
|
|
2571
|
+
if (domainRegex.test(domain)) {
|
|
2572
|
+
let processKeyword2 = function(keyword) {
|
|
2573
|
+
if ([import_pinggy5.TunnelType.Http, import_pinggy5.TunnelType.Tcp, import_pinggy5.TunnelType.Tls, import_pinggy5.TunnelType.Udp, import_pinggy5.TunnelType.TlsTcp].includes(keyword)) {
|
|
2574
|
+
type = keyword;
|
|
2575
|
+
} else if (keyword === "force") {
|
|
2576
|
+
forceFlag = true;
|
|
2577
|
+
} else if (keyword === "qr") {
|
|
2578
|
+
qrCode = true;
|
|
2452
2579
|
}
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2580
|
+
};
|
|
2581
|
+
var processKeyword = processKeyword2;
|
|
2582
|
+
server = domain;
|
|
2583
|
+
const parts = user.split("+");
|
|
2584
|
+
if (parts.length === 0) {
|
|
2585
|
+
return { token, type, server, qrCode, forceFlag };
|
|
2457
2586
|
}
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
response = await this.handleStartReq(req, raw);
|
|
2466
|
-
break;
|
|
2467
|
-
}
|
|
2468
|
-
case "stop": {
|
|
2469
|
-
response = await this.handleStopReq(req, raw);
|
|
2470
|
-
break;
|
|
2471
|
-
}
|
|
2472
|
-
case "get": {
|
|
2473
|
-
response = await this.handleGetReq(req, raw);
|
|
2474
|
-
break;
|
|
2475
|
-
}
|
|
2476
|
-
case "restart": {
|
|
2477
|
-
response = await this.handleRestartReq(req, raw);
|
|
2478
|
-
break;
|
|
2479
|
-
}
|
|
2480
|
-
case "updateconfig": {
|
|
2481
|
-
response = await this.handleUpdateConfigReq(req, raw);
|
|
2482
|
-
break;
|
|
2483
|
-
}
|
|
2484
|
-
case "list": {
|
|
2485
|
-
response = await this.handleListReq(req);
|
|
2486
|
-
break;
|
|
2487
|
-
}
|
|
2488
|
-
default:
|
|
2489
|
-
if (typeof req.command === "string") {
|
|
2490
|
-
logger.warn("Unknown command", { command: req.command });
|
|
2491
|
-
}
|
|
2492
|
-
return this.sendError(ws, req, "Invalid command");
|
|
2587
|
+
const firstPart = parts[0];
|
|
2588
|
+
if (!isKeyword(firstPart)) {
|
|
2589
|
+
token = firstPart;
|
|
2590
|
+
for (let i = 1; i < parts.length; i++) {
|
|
2591
|
+
const part = parts[i].toLowerCase();
|
|
2592
|
+
if (!isKeyword(part)) {
|
|
2593
|
+
throw new Error(`Invalid user format: unexpected token '${part}' when keywords are expected.`);
|
|
2493
2594
|
}
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2595
|
+
processKeyword2(part);
|
|
2596
|
+
}
|
|
2597
|
+
} else {
|
|
2598
|
+
for (const part of parts) {
|
|
2599
|
+
const lowerPart = part.toLowerCase();
|
|
2600
|
+
if (!isKeyword(lowerPart)) {
|
|
2601
|
+
throw new Error(`Invalid user format: unexpected token '${lowerPart}' when keywords are expected.`);
|
|
2500
2602
|
}
|
|
2501
|
-
|
|
2502
|
-
return this.sendError(ws, req, e?.message || "Internal error");
|
|
2603
|
+
processKeyword2(lowerPart);
|
|
2503
2604
|
}
|
|
2504
2605
|
}
|
|
2505
|
-
}
|
|
2606
|
+
}
|
|
2607
|
+
} else if (domainRegex.test(str)) {
|
|
2608
|
+
server = str;
|
|
2506
2609
|
}
|
|
2507
|
-
}
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
let
|
|
2512
|
-
|
|
2513
|
-
|
|
2610
|
+
return { token, type, server, qrCode, forceFlag };
|
|
2611
|
+
}
|
|
2612
|
+
function parseUsers(positionalArgs, explicitToken) {
|
|
2613
|
+
let token;
|
|
2614
|
+
let server;
|
|
2615
|
+
let type;
|
|
2616
|
+
let forceFlag = false;
|
|
2617
|
+
let qrCode = false;
|
|
2618
|
+
let remaining = [...positionalArgs];
|
|
2619
|
+
if (typeof explicitToken === "string") {
|
|
2620
|
+
const parsed = parseUserAndDomain(explicitToken);
|
|
2621
|
+
if (parsed.server) server = parsed.server;
|
|
2622
|
+
if (parsed.type) type = parsed.type;
|
|
2623
|
+
if (parsed.token) token = parsed.token;
|
|
2624
|
+
if (parsed.forceFlag) forceFlag = true;
|
|
2625
|
+
if (parsed.qrCode) qrCode = true;
|
|
2626
|
+
}
|
|
2627
|
+
if (remaining.length > 0) {
|
|
2628
|
+
const first = remaining[0];
|
|
2629
|
+
const parsed = parseUserAndDomain(first);
|
|
2630
|
+
if (parsed.server) {
|
|
2631
|
+
server = parsed.server;
|
|
2632
|
+
if (parsed.type) type = parsed.type;
|
|
2633
|
+
if (parsed.token) token = parsed.token;
|
|
2634
|
+
if (parsed.forceFlag) forceFlag = true;
|
|
2635
|
+
if (parsed.qrCode) qrCode = true;
|
|
2636
|
+
remaining = remaining.slice(1);
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
return { token, server, type, forceFlag, qrCode, remaining };
|
|
2640
|
+
}
|
|
2641
|
+
function parseType(finalConfig, values, inferredType) {
|
|
2642
|
+
const t = inferredType || values.type || finalConfig.tunnelType;
|
|
2643
|
+
if (t === import_pinggy5.TunnelType.Http || t === import_pinggy5.TunnelType.Tcp || t === import_pinggy5.TunnelType.Tls || t === import_pinggy5.TunnelType.Udp || t === import_pinggy5.TunnelType.TlsTcp) {
|
|
2644
|
+
finalConfig.tunnelType = [t];
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
function parseLocalPort(finalConfig, values) {
|
|
2648
|
+
if (typeof values.localport !== "string") return null;
|
|
2649
|
+
let lp = values.localport.trim();
|
|
2650
|
+
let isHttps = false;
|
|
2651
|
+
if (lp.startsWith("https://")) {
|
|
2652
|
+
isHttps = true;
|
|
2653
|
+
lp = lp.replace(/^https:\/\//, "");
|
|
2654
|
+
} else if (lp.startsWith("http://")) {
|
|
2655
|
+
lp = lp.replace(/^http:\/\//, "");
|
|
2656
|
+
}
|
|
2657
|
+
const parts = lp.split(":");
|
|
2658
|
+
if (parts.length === 1) {
|
|
2659
|
+
const port = parseInt(parts[0], 10);
|
|
2660
|
+
if (!Number.isNaN(port) && isValidPort(port)) {
|
|
2661
|
+
finalConfig.forwarding = `localhost:${port}`;
|
|
2662
|
+
} else {
|
|
2663
|
+
return new Error("Invalid local port");
|
|
2664
|
+
}
|
|
2665
|
+
} else if (parts.length === 2) {
|
|
2666
|
+
const host = parts[0] || "localhost";
|
|
2667
|
+
const port = parseInt(parts[1], 10);
|
|
2668
|
+
if (!Number.isNaN(port) && isValidPort(port)) {
|
|
2669
|
+
finalConfig.forwarding = `${host}:${port}`;
|
|
2670
|
+
} else {
|
|
2671
|
+
return new Error("Invalid local port. Please use -h option for help.");
|
|
2672
|
+
}
|
|
2673
|
+
} else {
|
|
2674
|
+
return new Error("Invalid --localport format. Please use -h option for help.");
|
|
2514
2675
|
}
|
|
2515
|
-
|
|
2516
|
-
return `${trimmed}/backend/api/v1/remote-management/connect`;
|
|
2676
|
+
return null;
|
|
2517
2677
|
}
|
|
2518
|
-
function
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
return url.host;
|
|
2522
|
-
} catch {
|
|
2523
|
-
return u;
|
|
2678
|
+
function removeIPv6Brackets(ip) {
|
|
2679
|
+
if (ip.startsWith("[") && ip.endsWith("]")) {
|
|
2680
|
+
return ip.slice(1, -1);
|
|
2524
2681
|
}
|
|
2682
|
+
return ip;
|
|
2525
2683
|
}
|
|
2526
|
-
function
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
}
|
|
2537
|
-
|
|
2538
|
-
|
|
2684
|
+
function ipv6SafeSplitColon(s) {
|
|
2685
|
+
const result = [];
|
|
2686
|
+
let buf = "";
|
|
2687
|
+
const stack = [];
|
|
2688
|
+
for (let i = 0; i < s.length; i++) {
|
|
2689
|
+
const c = s[i];
|
|
2690
|
+
if (c === "[") {
|
|
2691
|
+
stack.push(c);
|
|
2692
|
+
} else if (c === "]" && stack.length > 0) {
|
|
2693
|
+
stack.pop();
|
|
2694
|
+
}
|
|
2695
|
+
if (c === ":" && stack.length === 0) {
|
|
2696
|
+
result.push(buf);
|
|
2697
|
+
buf = "";
|
|
2698
|
+
} else {
|
|
2699
|
+
buf += c;
|
|
2539
2700
|
}
|
|
2540
2701
|
}
|
|
2702
|
+
result.push(buf);
|
|
2703
|
+
return result;
|
|
2541
2704
|
}
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2705
|
+
function parseDefaultForwarding(forwarding) {
|
|
2706
|
+
const parts = ipv6SafeSplitColon(forwarding);
|
|
2707
|
+
if (parts.length === 3) {
|
|
2708
|
+
const remotePort = parseInt(parts[0], 10);
|
|
2709
|
+
const localDomain = removeIPv6Brackets(parts[1] || "localhost");
|
|
2710
|
+
const localPort = parseInt(parts[2], 10);
|
|
2711
|
+
return { remotePort, localDomain, localPort };
|
|
2545
2712
|
}
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2713
|
+
if (parts.length === 4) {
|
|
2714
|
+
const remoteDomain = removeIPv6Brackets(parts[0]);
|
|
2715
|
+
const remotePort = parseInt(parts[1], 10);
|
|
2716
|
+
const localDomain = removeIPv6Brackets(parts[2] || "localhost");
|
|
2717
|
+
const localPort = parseInt(parts[3], 10);
|
|
2718
|
+
return { remoteDomain, remotePort, localDomain, localPort };
|
|
2719
|
+
}
|
|
2720
|
+
return new Error("forwarding address incorrect");
|
|
2721
|
+
}
|
|
2722
|
+
function parseAdditionalForwarding(forwarding) {
|
|
2723
|
+
const toPort = (v) => {
|
|
2724
|
+
if (!v) return null;
|
|
2725
|
+
const n = parseInt(v, 10);
|
|
2726
|
+
return Number.isNaN(n) ? null : n;
|
|
2552
2727
|
};
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2728
|
+
const parsed = ipv6SafeSplitColon(forwarding);
|
|
2729
|
+
if (parsed.length !== 4) {
|
|
2730
|
+
return new Error(
|
|
2731
|
+
"forwarding must be in format: [schema//]hostname[/port][@forwardingId]:<placeholder>:<forwardingAddress>:<forwardingPort>"
|
|
2732
|
+
);
|
|
2733
|
+
}
|
|
2734
|
+
const firstPart = parsed[0];
|
|
2735
|
+
const [hostPart] = firstPart.split("@");
|
|
2736
|
+
let protocol = "http";
|
|
2737
|
+
let remoteDomainRaw;
|
|
2738
|
+
let remotePort = 0;
|
|
2739
|
+
if (hostPart.includes("//")) {
|
|
2740
|
+
const [schema, rest] = hostPart.split("//");
|
|
2741
|
+
if (!schema || !VALID_PROTOCOLS.includes(schema)) {
|
|
2742
|
+
return new Error(`invalid protocol: ${schema}`);
|
|
2743
|
+
}
|
|
2744
|
+
protocol = schema;
|
|
2745
|
+
const domainAndPort = rest.split("/");
|
|
2746
|
+
if (domainAndPort.length > 2) {
|
|
2747
|
+
return new Error("invalid forwarding address format");
|
|
2748
|
+
}
|
|
2749
|
+
remoteDomainRaw = domainAndPort[0];
|
|
2750
|
+
if (!remoteDomainRaw || !domainRegex.test(remoteDomainRaw)) {
|
|
2751
|
+
return new Error("invalid remote domain");
|
|
2752
|
+
}
|
|
2753
|
+
const parsedRemotePort = toPort(domainAndPort[1]);
|
|
2754
|
+
if (protocol === "http") {
|
|
2755
|
+
remotePort = 0;
|
|
2756
|
+
} else {
|
|
2757
|
+
if (parsedRemotePort === null || !isValidPort(parsedRemotePort)) {
|
|
2758
|
+
return new Error(
|
|
2759
|
+
`${protocol} forwarding requires port in format ${protocol}//domain/remotePort`
|
|
2760
|
+
);
|
|
2761
|
+
}
|
|
2762
|
+
remotePort = parsedRemotePort;
|
|
2763
|
+
}
|
|
2764
|
+
} else {
|
|
2765
|
+
remoteDomainRaw = hostPart;
|
|
2766
|
+
if (!domainRegex.test(remoteDomainRaw)) {
|
|
2767
|
+
return new Error("invalid remote domain");
|
|
2768
|
+
}
|
|
2769
|
+
protocol = "http";
|
|
2770
|
+
remotePort = 0;
|
|
2771
|
+
}
|
|
2772
|
+
const localDomain = removeIPv6Brackets(parsed[2] || "localhost");
|
|
2773
|
+
const localPort = toPort(parsed[3]);
|
|
2774
|
+
if (localPort === null || !isValidPort(localPort)) {
|
|
2775
|
+
return new Error("forwarding address incorrect: invalid local port");
|
|
2776
|
+
}
|
|
2777
|
+
return {
|
|
2778
|
+
protocol,
|
|
2779
|
+
remoteDomain: remoteDomainRaw,
|
|
2780
|
+
remotePort,
|
|
2781
|
+
localDomain,
|
|
2782
|
+
localPort
|
|
2557
2783
|
};
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2784
|
+
}
|
|
2785
|
+
function parseReverseTunnelAddr(finalConfig, values) {
|
|
2786
|
+
const reverseTunnel = values.R;
|
|
2787
|
+
if ((!Array.isArray(reverseTunnel) || reverseTunnel.length === 0) && !values.localport && !finalConfig.forwarding) {
|
|
2788
|
+
return new Error("local port not specified. Please use '-h' option for help.");
|
|
2789
|
+
}
|
|
2790
|
+
if (!Array.isArray(reverseTunnel) || reverseTunnel.length === 0) {
|
|
2791
|
+
return null;
|
|
2792
|
+
}
|
|
2793
|
+
for (const forwarding of reverseTunnel) {
|
|
2794
|
+
const slicedForwarding = ipv6SafeSplitColon(forwarding);
|
|
2795
|
+
if (slicedForwarding.length === 3) {
|
|
2796
|
+
const parsed = parseDefaultForwarding(forwarding);
|
|
2797
|
+
if (parsed instanceof Error) return parsed;
|
|
2798
|
+
finalConfig.forwarding = `${parsed.localDomain}:${parsed.localPort}`;
|
|
2799
|
+
} else if (slicedForwarding.length === 4) {
|
|
2800
|
+
finalConfig.additionalForwarding ?? (finalConfig.additionalForwarding = []);
|
|
2801
|
+
const parsed = parseAdditionalForwarding(forwarding);
|
|
2802
|
+
if (parsed instanceof Error) return parsed;
|
|
2803
|
+
finalConfig.additionalForwarding.push(parsed);
|
|
2804
|
+
} else {
|
|
2805
|
+
return new Error(
|
|
2806
|
+
"Incorrect command line arguments: reverse tunnel address incorrect. Please use '-h' option for help."
|
|
2807
|
+
);
|
|
2565
2808
|
}
|
|
2566
|
-
if (_stopRequested) break;
|
|
2567
|
-
printer_default.warn(`Remote management disconnected. Reconnecting in ${RECONNECT_SLEEP_MS / 1e3} seconds...`);
|
|
2568
|
-
logger.info("Reconnecting to remote management after disconnect");
|
|
2569
|
-
await sleep(RECONNECT_SLEEP_MS);
|
|
2570
2809
|
}
|
|
2571
|
-
|
|
2572
|
-
logger.info("Remote management stopped.");
|
|
2573
|
-
return getRemoteManagementState();
|
|
2810
|
+
return null;
|
|
2574
2811
|
}
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
printer_default.success(`Connected to ${wsHost}`);
|
|
2590
|
-
heartbeat = setInterval(() => {
|
|
2591
|
-
if (ws.readyState === import_ws.default.OPEN) ws.ping();
|
|
2592
|
-
}, PING_INTERVAL_MS);
|
|
2593
|
-
});
|
|
2594
|
-
ws.on("ping", () => ws.pong());
|
|
2595
|
-
ws.on("message", async (data) => {
|
|
2596
|
-
try {
|
|
2597
|
-
if (firstMessage) {
|
|
2598
|
-
firstMessage = false;
|
|
2599
|
-
const ok = handleConnectionStatusMessage(data);
|
|
2600
|
-
if (!ok) ws.close();
|
|
2601
|
-
return;
|
|
2602
|
-
}
|
|
2603
|
-
setRemoteManagementState({ status: RemoteManagementStatus.Running, errorMessage: "" });
|
|
2604
|
-
const req = JSON.parse(data.toString("utf8"));
|
|
2605
|
-
await new WebSocketCommandHandler().handle(ws, req);
|
|
2606
|
-
} catch (e) {
|
|
2607
|
-
logger.warn("Failed handling websocket message", { error: String(e) });
|
|
2608
|
-
}
|
|
2609
|
-
});
|
|
2610
|
-
ws.on("unexpected-response", (_, res) => {
|
|
2611
|
-
setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: `HTTP ${res.statusCode}` });
|
|
2612
|
-
if (res.statusCode === 401) {
|
|
2613
|
-
printer_default.error("Unauthorized. Please enter a valid token.");
|
|
2614
|
-
logger.error("Unauthorized (401) on remote management connect");
|
|
2615
|
-
} else {
|
|
2616
|
-
printer_default.warn(`Unexpected HTTP ${res.statusCode}. Retrying...`);
|
|
2617
|
-
logger.warn("Unexpected HTTP response", { statusCode: res.statusCode });
|
|
2618
|
-
}
|
|
2619
|
-
ws.close();
|
|
2620
|
-
});
|
|
2621
|
-
ws.on("close", (code, reason) => {
|
|
2622
|
-
setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: "" });
|
|
2623
|
-
logger.info("WebSocket closed", { code, reason: reason.toString() });
|
|
2624
|
-
printer_default.warn(`Disconnected (code: ${code}). Retrying...`);
|
|
2625
|
-
cleanup();
|
|
2626
|
-
});
|
|
2627
|
-
ws.on("error", (err) => {
|
|
2628
|
-
setRemoteManagementState({ status: RemoteManagementStatus.Error, errorMessage: err.message });
|
|
2629
|
-
logger.warn("WebSocket error", { error: err.message });
|
|
2630
|
-
printer_default.error(err);
|
|
2631
|
-
cleanup();
|
|
2632
|
-
});
|
|
2633
|
-
});
|
|
2812
|
+
function parseLocalTunnelAddr(finalConfig, values) {
|
|
2813
|
+
if (!Array.isArray(values.L) || values.L.length === 0) return null;
|
|
2814
|
+
const firstL = values.L[0];
|
|
2815
|
+
const parts = firstL.split(":");
|
|
2816
|
+
if (parts.length === 3) {
|
|
2817
|
+
const lp = parseInt(parts[0], 10);
|
|
2818
|
+
if (!Number.isNaN(lp) && isValidPort(lp)) {
|
|
2819
|
+
finalConfig.webDebugger = `localhost:${lp}`;
|
|
2820
|
+
} else {
|
|
2821
|
+
return new Error(`Invalid debugger port ${lp}`);
|
|
2822
|
+
}
|
|
2823
|
+
} else {
|
|
2824
|
+
return new Error("Incorrect command line arguments: web debugger address incorrect. Please use '-h' option for help.");
|
|
2825
|
+
}
|
|
2634
2826
|
}
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2827
|
+
function parseDebugger(finalConfig, values) {
|
|
2828
|
+
let dbg = values.debugger;
|
|
2829
|
+
if (typeof dbg !== "string") return;
|
|
2830
|
+
dbg = dbg.startsWith(":") ? dbg.slice(1) : dbg;
|
|
2831
|
+
const d = parseInt(dbg, 10);
|
|
2832
|
+
if (!Number.isNaN(d) && isValidPort(d)) {
|
|
2833
|
+
finalConfig.webDebugger = `localhost:${d}`;
|
|
2834
|
+
} else {
|
|
2835
|
+
logger.error("Invalid debugger port:", dbg);
|
|
2836
|
+
return new Error(`Invalid debugger port ${dbg}. Please use '-h' option for help.`);
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
function parseToken(finalConfig, explicitToken) {
|
|
2840
|
+
if (typeof explicitToken === "string" && explicitToken) {
|
|
2841
|
+
finalConfig.token = explicitToken;
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
function parseArgs(finalConfig, remainingPositionals) {
|
|
2845
|
+
parseExtendedOptions(remainingPositionals, finalConfig);
|
|
2846
|
+
}
|
|
2847
|
+
function storeJson(config, saveconf) {
|
|
2848
|
+
if (saveconf) {
|
|
2849
|
+
const path5 = saveconf;
|
|
2850
|
+
try {
|
|
2851
|
+
import_fs3.default.writeFileSync(path5, JSON.stringify(config, null, 2), { encoding: "utf-8", flag: "w" });
|
|
2852
|
+
logger.info(`Configuration saved to ${path5}`);
|
|
2853
|
+
} catch (err) {
|
|
2854
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2855
|
+
logger.error("Error loading configuration:", msg);
|
|
2645
2856
|
}
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
function loadJsonConfig(config) {
|
|
2860
|
+
const configpath = config["conf"];
|
|
2861
|
+
if (typeof configpath === "string" && configpath.trim().length > 0) {
|
|
2862
|
+
const filepath = import_path3.default.resolve(configpath);
|
|
2863
|
+
try {
|
|
2864
|
+
const data = import_fs3.default.readFileSync(filepath, { encoding: "utf-8" });
|
|
2865
|
+
const json = JSON.parse(data);
|
|
2866
|
+
return json;
|
|
2867
|
+
} catch (err) {
|
|
2868
|
+
logger.error("Error loading configuration:", err);
|
|
2653
2869
|
}
|
|
2654
|
-
} finally {
|
|
2655
|
-
currentWs = null;
|
|
2656
|
-
setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: "" });
|
|
2657
|
-
return getRemoteManagementState();
|
|
2658
2870
|
}
|
|
2871
|
+
return null;
|
|
2872
|
+
}
|
|
2873
|
+
function isSaveConfOption(values) {
|
|
2874
|
+
const saveconf = values["saveconf"];
|
|
2875
|
+
if (typeof saveconf === "string" && saveconf.trim().length > 0) {
|
|
2876
|
+
return saveconf;
|
|
2877
|
+
}
|
|
2878
|
+
return null;
|
|
2659
2879
|
}
|
|
2660
|
-
function
|
|
2661
|
-
|
|
2880
|
+
function parseServe(finalConfig, values) {
|
|
2881
|
+
const sv = values.serve;
|
|
2882
|
+
if (typeof sv !== "string" || sv.trim().length === 0) return null;
|
|
2883
|
+
finalConfig.serve = sv;
|
|
2884
|
+
return null;
|
|
2662
2885
|
}
|
|
2663
|
-
function
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2886
|
+
function parseAutoReconnect(finalConfig, values) {
|
|
2887
|
+
const autoReconnectValue = values.autoreconnect;
|
|
2888
|
+
if (typeof autoReconnectValue === "string") {
|
|
2889
|
+
const trimmed = autoReconnectValue.trim().toLowerCase();
|
|
2890
|
+
if (trimmed === "true" || trimmed === "") {
|
|
2891
|
+
finalConfig.autoReconnect = true;
|
|
2892
|
+
} else if (trimmed === "false") {
|
|
2893
|
+
finalConfig.autoReconnect = false;
|
|
2894
|
+
} else {
|
|
2895
|
+
return new Error(`Invalid autoreconnect value: ${autoReconnectValue}. Use true or false.`);
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
return null;
|
|
2899
|
+
}
|
|
2900
|
+
async function buildFinalConfig(values, positionals) {
|
|
2901
|
+
let token;
|
|
2902
|
+
let server;
|
|
2903
|
+
let type;
|
|
2904
|
+
let forceFlag = false;
|
|
2905
|
+
let qrCode = false;
|
|
2906
|
+
let finalConfig = new Object();
|
|
2907
|
+
let saveconf = isSaveConfOption(values);
|
|
2908
|
+
const configFromFile = loadJsonConfig(values);
|
|
2909
|
+
const userParse = parseUsers(positionals, values.token);
|
|
2910
|
+
token = userParse.token;
|
|
2911
|
+
server = userParse.server;
|
|
2912
|
+
type = userParse.type;
|
|
2913
|
+
forceFlag = userParse.forceFlag;
|
|
2914
|
+
qrCode = userParse.qrCode;
|
|
2915
|
+
const remainingPositionals = userParse.remaining;
|
|
2916
|
+
const initialTunnel = type || values.type;
|
|
2917
|
+
finalConfig = {
|
|
2918
|
+
...defaultOptions,
|
|
2919
|
+
...configFromFile || {},
|
|
2920
|
+
// Apply loaded config on top of defaults
|
|
2921
|
+
configid: getRandomId(),
|
|
2922
|
+
token: token || (configFromFile?.token || (typeof values.token === "string" ? values.token : "")),
|
|
2923
|
+
serverAddress: server || (configFromFile?.serverAddress || defaultOptions.serverAddress),
|
|
2924
|
+
tunnelType: initialTunnel ? [initialTunnel] : configFromFile?.tunnelType || [import_pinggy5.TunnelType.Http],
|
|
2925
|
+
NoTUI: values.notui || (configFromFile?.NoTUI || false),
|
|
2926
|
+
qrCode: qrCode || (configFromFile?.qrCode || false),
|
|
2927
|
+
autoReconnect: configFromFile?.autoReconnect ? configFromFile.autoReconnect : defaultOptions.autoReconnect
|
|
2667
2928
|
};
|
|
2929
|
+
parseType(finalConfig, values, type);
|
|
2930
|
+
parseToken(finalConfig, token || values.token);
|
|
2931
|
+
const dbgErr = parseDebugger(finalConfig, values);
|
|
2932
|
+
if (dbgErr instanceof Error) throw dbgErr;
|
|
2933
|
+
const lpErr = parseLocalPort(finalConfig, values);
|
|
2934
|
+
if (lpErr instanceof Error) throw lpErr;
|
|
2935
|
+
const rErr = parseReverseTunnelAddr(finalConfig, values);
|
|
2936
|
+
if (rErr instanceof Error) throw rErr;
|
|
2937
|
+
const lErr = parseLocalTunnelAddr(finalConfig, values);
|
|
2938
|
+
if (lErr instanceof Error) throw lErr;
|
|
2939
|
+
const serveErr = parseServe(finalConfig, values);
|
|
2940
|
+
if (serveErr instanceof Error) throw serveErr;
|
|
2941
|
+
const autoReconnectErr = parseAutoReconnect(finalConfig, values);
|
|
2942
|
+
if (autoReconnectErr instanceof Error) throw autoReconnectErr;
|
|
2943
|
+
if (forceFlag) finalConfig.force = true;
|
|
2944
|
+
parseArgs(finalConfig, remainingPositionals);
|
|
2945
|
+
storeJson(finalConfig, saveconf);
|
|
2946
|
+
return finalConfig;
|
|
2668
2947
|
}
|
|
2669
|
-
var
|
|
2670
|
-
var
|
|
2671
|
-
"src/
|
|
2948
|
+
var import_pinggy5, import_fs3, import_path3, domainRegex, KEYWORDS, VALID_PROTOCOLS;
|
|
2949
|
+
var init_buildConfig = __esm({
|
|
2950
|
+
"src/cli/buildConfig.ts"() {
|
|
2672
2951
|
"use strict";
|
|
2673
2952
|
init_cjs_shims();
|
|
2674
|
-
|
|
2953
|
+
init_defaults();
|
|
2954
|
+
init_extendedOptions();
|
|
2675
2955
|
init_logger();
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2956
|
+
init_util();
|
|
2957
|
+
import_pinggy5 = require("@pinggy/pinggy");
|
|
2958
|
+
import_fs3 = __toESM(require("fs"), 1);
|
|
2959
|
+
import_path3 = __toESM(require("path"), 1);
|
|
2960
|
+
domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
|
|
2961
|
+
KEYWORDS = /* @__PURE__ */ new Set([
|
|
2962
|
+
import_pinggy5.TunnelType.Http,
|
|
2963
|
+
import_pinggy5.TunnelType.Tcp,
|
|
2964
|
+
import_pinggy5.TunnelType.Tls,
|
|
2965
|
+
import_pinggy5.TunnelType.Udp,
|
|
2966
|
+
import_pinggy5.TunnelType.TlsTcp,
|
|
2967
|
+
"force",
|
|
2968
|
+
"qr"
|
|
2969
|
+
]);
|
|
2970
|
+
VALID_PROTOCOLS = ["http", "tcp", "udp", "tls"];
|
|
2687
2971
|
}
|
|
2688
2972
|
});
|
|
2689
2973
|
|
|
@@ -3486,6 +3770,91 @@ function closeDisconnectModal(screen, manager) {
|
|
|
3486
3770
|
manager.inDisconnectView = false;
|
|
3487
3771
|
screen.render();
|
|
3488
3772
|
}
|
|
3773
|
+
function showReconnectingModal(screen, manager, retryCnt, message) {
|
|
3774
|
+
if (manager.reconnectModal) {
|
|
3775
|
+
manager.reconnectModal.destroy();
|
|
3776
|
+
manager.reconnectModal = null;
|
|
3777
|
+
}
|
|
3778
|
+
manager.inReconnectView = true;
|
|
3779
|
+
manager.reconnectModal = import_blessed2.default.box({
|
|
3780
|
+
parent: screen,
|
|
3781
|
+
top: "center",
|
|
3782
|
+
left: "center",
|
|
3783
|
+
width: "50%",
|
|
3784
|
+
height: "20%",
|
|
3785
|
+
border: {
|
|
3786
|
+
type: "line"
|
|
3787
|
+
},
|
|
3788
|
+
style: {
|
|
3789
|
+
border: {
|
|
3790
|
+
fg: "yellow"
|
|
3791
|
+
}
|
|
3792
|
+
},
|
|
3793
|
+
padding: { left: 2, right: 2, top: 1, bottom: 1 },
|
|
3794
|
+
tags: true,
|
|
3795
|
+
align: "center",
|
|
3796
|
+
valign: "middle"
|
|
3797
|
+
});
|
|
3798
|
+
const content = `{yellow-fg}{bold}Reconnecting...{/bold}{/yellow-fg}
|
|
3799
|
+
|
|
3800
|
+
${message || `Attempt #${retryCnt} \u2014 trying to re-establish tunnel...`}
|
|
3801
|
+
|
|
3802
|
+
{gray-fg}Please wait{/gray-fg}`;
|
|
3803
|
+
manager.reconnectModal.setContent(content);
|
|
3804
|
+
manager.reconnectModal.focus();
|
|
3805
|
+
screen.render();
|
|
3806
|
+
}
|
|
3807
|
+
function closeReconnectingModal(screen, manager) {
|
|
3808
|
+
if (manager.reconnectModal) {
|
|
3809
|
+
manager.reconnectModal.destroy();
|
|
3810
|
+
manager.reconnectModal = null;
|
|
3811
|
+
}
|
|
3812
|
+
manager.inReconnectView = false;
|
|
3813
|
+
screen.render();
|
|
3814
|
+
}
|
|
3815
|
+
function showReconnectionFailedModal(screen, manager, retryCnt, onClose) {
|
|
3816
|
+
closeReconnectingModal(screen, manager);
|
|
3817
|
+
manager.inReconnectView = true;
|
|
3818
|
+
manager.reconnectModal = import_blessed2.default.box({
|
|
3819
|
+
parent: screen,
|
|
3820
|
+
top: "center",
|
|
3821
|
+
left: "center",
|
|
3822
|
+
width: "50%",
|
|
3823
|
+
height: "20%",
|
|
3824
|
+
border: {
|
|
3825
|
+
type: "line"
|
|
3826
|
+
},
|
|
3827
|
+
style: {
|
|
3828
|
+
border: {
|
|
3829
|
+
fg: "red"
|
|
3830
|
+
}
|
|
3831
|
+
},
|
|
3832
|
+
padding: { left: 2, right: 2, top: 1, bottom: 1 },
|
|
3833
|
+
tags: true,
|
|
3834
|
+
align: "center",
|
|
3835
|
+
valign: "middle"
|
|
3836
|
+
});
|
|
3837
|
+
const content = `{red-fg}{bold}Reconnection Failed{/bold}{/red-fg}
|
|
3838
|
+
|
|
3839
|
+
Failed to reconnect after ${retryCnt} attempts.
|
|
3840
|
+
Tunnel will be closed.
|
|
3841
|
+
|
|
3842
|
+
{white-bg}{black-fg}Closing in 5 seconds...{/black-fg}{/white-bg}`;
|
|
3843
|
+
manager.reconnectModal.setContent(content);
|
|
3844
|
+
manager.reconnectModal.focus();
|
|
3845
|
+
screen.render();
|
|
3846
|
+
const timeout = setTimeout(() => {
|
|
3847
|
+
closeReconnectingModal(screen, manager);
|
|
3848
|
+
if (onClose) onClose();
|
|
3849
|
+
}, 5e3);
|
|
3850
|
+
const keyHandler = () => {
|
|
3851
|
+
clearTimeout(timeout);
|
|
3852
|
+
closeReconnectingModal(screen, manager);
|
|
3853
|
+
if (onClose) onClose();
|
|
3854
|
+
};
|
|
3855
|
+
manager.reconnectModal.key(["escape", "enter", "space"], keyHandler);
|
|
3856
|
+
screen.key(["escape", "enter", "space"], keyHandler);
|
|
3857
|
+
}
|
|
3489
3858
|
function showLoadingModal(screen, modalManager, message = "Loading...") {
|
|
3490
3859
|
if (modalManager.loadingView) return;
|
|
3491
3860
|
modalManager.loadingBox = import_blessed2.default.box({
|
|
@@ -3793,9 +4162,11 @@ var init_TunnelTui = __esm({
|
|
|
3793
4162
|
detailModal: null,
|
|
3794
4163
|
keyBindingsModal: null,
|
|
3795
4164
|
disconnectModal: null,
|
|
4165
|
+
reconnectModal: null,
|
|
3796
4166
|
inDetailView: false,
|
|
3797
4167
|
keyBindingView: false,
|
|
3798
4168
|
inDisconnectView: false,
|
|
4169
|
+
inReconnectView: false,
|
|
3799
4170
|
loadingBox: null,
|
|
3800
4171
|
loadingView: false,
|
|
3801
4172
|
fetchAbortController: null
|
|
@@ -3996,6 +4367,25 @@ Tunnel will be closed.` : info.messages?.join("\n") || "Disconnect request recei
|
|
|
3996
4367
|
);
|
|
3997
4368
|
}
|
|
3998
4369
|
}
|
|
4370
|
+
updateReconnectingInfo(retryCnt, message) {
|
|
4371
|
+
showReconnectingModal(
|
|
4372
|
+
this.screen,
|
|
4373
|
+
this.modalManager,
|
|
4374
|
+
retryCnt,
|
|
4375
|
+
message
|
|
4376
|
+
);
|
|
4377
|
+
}
|
|
4378
|
+
closeReconnectingInfo() {
|
|
4379
|
+
closeReconnectingModal(this.screen, this.modalManager);
|
|
4380
|
+
}
|
|
4381
|
+
updateReconnectionFailed(retryCnt) {
|
|
4382
|
+
showReconnectionFailedModal(
|
|
4383
|
+
this.screen,
|
|
4384
|
+
this.modalManager,
|
|
4385
|
+
retryCnt,
|
|
4386
|
+
() => this.destroy()
|
|
4387
|
+
);
|
|
4388
|
+
}
|
|
3999
4389
|
start() {
|
|
4000
4390
|
this.screen.render();
|
|
4001
4391
|
}
|
|
@@ -4096,6 +4486,35 @@ async function startCli(finalConfig, manager) {
|
|
|
4096
4486
|
}
|
|
4097
4487
|
printer_default.print(import_picocolors3.default.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"));
|
|
4098
4488
|
printer_default.print(import_picocolors3.default.gray("\nPress Ctrl+C to stop the tunnel.\n"));
|
|
4489
|
+
manager2.registerWillReconnectListener(tunnel.tunnelid, (tunnelId, error, messages) => {
|
|
4490
|
+
if (activeTui) {
|
|
4491
|
+
const msg = messages?.join("\n") || error || "Tunnel disconnected, reconnecting...";
|
|
4492
|
+
activeTui.updateReconnectingInfo(0, msg);
|
|
4493
|
+
} else if (finalConfig.autoReconnect) {
|
|
4494
|
+
printer_default.warn(error || "Tunnel connection reset");
|
|
4495
|
+
printer_default.startSpinner(messages?.join("\n"));
|
|
4496
|
+
}
|
|
4497
|
+
});
|
|
4498
|
+
manager2.registerReconnectingListener(tunnel.tunnelid, (tunnelId, retryCnt) => {
|
|
4499
|
+
if (activeTui) {
|
|
4500
|
+
activeTui.updateReconnectingInfo(retryCnt);
|
|
4501
|
+
} else if (finalConfig.autoReconnect) {
|
|
4502
|
+
printer_default.startSpinner(`Reconnecting to Pinggy (attempt #${retryCnt})`);
|
|
4503
|
+
}
|
|
4504
|
+
});
|
|
4505
|
+
manager2.registerReconnectionCompletedListener(tunnel.tunnelid, (tunnelId, urls) => {
|
|
4506
|
+
if (activeTui) {
|
|
4507
|
+
activeTui.closeReconnectingInfo();
|
|
4508
|
+
}
|
|
4509
|
+
});
|
|
4510
|
+
manager2.registerReconnectionFailedListener(tunnel.tunnelid, (tunnelId, retryCnt) => {
|
|
4511
|
+
if (activeTui) {
|
|
4512
|
+
activeTui.updateReconnectionFailed(retryCnt);
|
|
4513
|
+
} else {
|
|
4514
|
+
printer_default.stopSpinnerFail(`Reconnection failed after ${retryCnt} attempts`);
|
|
4515
|
+
process.exit(1);
|
|
4516
|
+
}
|
|
4517
|
+
});
|
|
4099
4518
|
manager2.registerDisconnectListener(tunnel.tunnelid, async (tunnelId, error, messages) => {
|
|
4100
4519
|
if (activeTui) {
|
|
4101
4520
|
disconnectState = {
|
|
@@ -4273,6 +4692,19 @@ var init_main = __esm({
|
|
|
4273
4692
|
});
|
|
4274
4693
|
|
|
4275
4694
|
// src/index.ts
|
|
4695
|
+
var index_exports = {};
|
|
4696
|
+
__export(index_exports, {
|
|
4697
|
+
TunnelErrorCodeType: () => TunnelErrorCodeType,
|
|
4698
|
+
TunnelManager: () => TunnelManager,
|
|
4699
|
+
TunnelOperations: () => TunnelOperations,
|
|
4700
|
+
TunnelStateType: () => TunnelStateType,
|
|
4701
|
+
TunnelWarningCode: () => TunnelWarningCode,
|
|
4702
|
+
closeRemoteManagement: () => closeRemoteManagement,
|
|
4703
|
+
enablePackageLogging: () => enablePackageLogging,
|
|
4704
|
+
getRemoteManagementState: () => getRemoteManagementState,
|
|
4705
|
+
initiateRemoteManagement: () => initiateRemoteManagement
|
|
4706
|
+
});
|
|
4707
|
+
module.exports = __toCommonJS(index_exports);
|
|
4276
4708
|
init_cjs_shims();
|
|
4277
4709
|
|
|
4278
4710
|
// src/utils/detect_vc_redist_on_windows.ts
|
|
@@ -4357,6 +4789,11 @@ async function openDownloadPage() {
|
|
|
4357
4789
|
|
|
4358
4790
|
// src/index.ts
|
|
4359
4791
|
init_printer();
|
|
4792
|
+
init_TunnelManager();
|
|
4793
|
+
init_handler();
|
|
4794
|
+
init_logger();
|
|
4795
|
+
init_remoteManagement();
|
|
4796
|
+
init_types();
|
|
4360
4797
|
async function verifyAndLoad() {
|
|
4361
4798
|
if (process.platform === "win32") {
|
|
4362
4799
|
const vcRedist = checkVCRedist();
|
|
@@ -4374,3 +4811,15 @@ verifyAndLoad().catch((err) => {
|
|
|
4374
4811
|
printer_default.error(`Failed to start CLI:, ${err}`);
|
|
4375
4812
|
process.exit(1);
|
|
4376
4813
|
});
|
|
4814
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
4815
|
+
0 && (module.exports = {
|
|
4816
|
+
TunnelErrorCodeType,
|
|
4817
|
+
TunnelManager,
|
|
4818
|
+
TunnelOperations,
|
|
4819
|
+
TunnelStateType,
|
|
4820
|
+
TunnelWarningCode,
|
|
4821
|
+
closeRemoteManagement,
|
|
4822
|
+
enablePackageLogging,
|
|
4823
|
+
getRemoteManagementState,
|
|
4824
|
+
initiateRemoteManagement
|
|
4825
|
+
});
|