pinggy 0.3.4 → 0.3.6
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 +1814 -1362
- package/dist/index.d.cts +616 -0
- package/dist/index.d.ts +616 -0
- package/dist/index.js +38 -55
- package/dist/{main-CZY6GID4.js → main-2QDG7PWL.js} +229 -1726
- package/package.json +3 -4
- 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 -475
- 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 -30
- 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 -167
- 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,1397 +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: "boolean", short: "a", description: "Automatically reconnect tunnel on failure." },
|
|
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: false
|
|
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
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
token = (token ? token + "+" : "") + part;
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
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 || ""
|
|
1531
1650
|
}
|
|
1532
|
-
}
|
|
1533
|
-
server = str;
|
|
1534
|
-
}
|
|
1535
|
-
return { token, type, server, qrCode };
|
|
1651
|
+
};
|
|
1536
1652
|
}
|
|
1537
|
-
function
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
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
|
+
});
|
|
1757
|
+
}
|
|
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
|
+
});
|
|
1549
1775
|
}
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
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 || "";
|
|
1800
|
+
}
|
|
1801
|
+
} catch (e) {
|
|
1564
1802
|
}
|
|
1803
|
+
return status;
|
|
1565
1804
|
}
|
|
1566
|
-
|
|
1567
|
-
|
|
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
|
+
};
|
|
1568
1821
|
}
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
}
|
|
1574
|
-
function parseType(finalConfig, values, inferredType) {
|
|
1575
|
-
const t = inferredType || values.type || finalConfig.tunnelType;
|
|
1576
|
-
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) {
|
|
1577
|
-
finalConfig.tunnelType = [t];
|
|
1578
|
-
}
|
|
1579
|
-
}
|
|
1580
|
-
function parseLocalPort(finalConfig, values) {
|
|
1581
|
-
if (typeof values.localport !== "string") return null;
|
|
1582
|
-
let lp = values.localport.trim();
|
|
1583
|
-
let isHttps = false;
|
|
1584
|
-
if (lp.startsWith("https://")) {
|
|
1585
|
-
isHttps = true;
|
|
1586
|
-
lp = lp.replace(/^https:\/\//, "");
|
|
1587
|
-
} else if (lp.startsWith("http://")) {
|
|
1588
|
-
lp = lp.replace(/^http:\/\//, "");
|
|
1589
|
-
}
|
|
1590
|
-
const parts = lp.split(":");
|
|
1591
|
-
if (parts.length === 1) {
|
|
1592
|
-
const port = parseInt(parts[0], 10);
|
|
1593
|
-
if (!Number.isNaN(port) && isValidPort(port)) {
|
|
1594
|
-
finalConfig.forwarding = `localhost:${port}`;
|
|
1595
|
-
} else {
|
|
1596
|
-
return new Error("Invalid local port");
|
|
1597
|
-
}
|
|
1598
|
-
} else if (parts.length === 2) {
|
|
1599
|
-
const host = parts[0] || "localhost";
|
|
1600
|
-
const port = parseInt(parts[1], 10);
|
|
1601
|
-
if (!Number.isNaN(port) && isValidPort(port)) {
|
|
1602
|
-
finalConfig.forwarding = `${host}:${port}`;
|
|
1603
|
-
} else {
|
|
1604
|
-
return new Error("Invalid local port. Please use -h option for help.");
|
|
1605
|
-
}
|
|
1606
|
-
} else {
|
|
1607
|
-
return new Error("Invalid --localport format. Please use -h option for help.");
|
|
1608
|
-
}
|
|
1609
|
-
return null;
|
|
1610
|
-
}
|
|
1611
|
-
function removeIPv6Brackets(ip) {
|
|
1612
|
-
if (ip.startsWith("[") && ip.endsWith("]")) {
|
|
1613
|
-
return ip.slice(1, -1);
|
|
1614
|
-
}
|
|
1615
|
-
return ip;
|
|
1616
|
-
}
|
|
1617
|
-
function ipv6SafeSplitColon(s) {
|
|
1618
|
-
const result = [];
|
|
1619
|
-
let buf = "";
|
|
1620
|
-
const stack = [];
|
|
1621
|
-
for (let i = 0; i < s.length; i++) {
|
|
1622
|
-
const c = s[i];
|
|
1623
|
-
if (c === "[") {
|
|
1624
|
-
stack.push(c);
|
|
1625
|
-
} else if (c === "]" && stack.length > 0) {
|
|
1626
|
-
stack.pop();
|
|
1627
|
-
}
|
|
1628
|
-
if (c === ":" && stack.length === 0) {
|
|
1629
|
-
result.push(buf);
|
|
1630
|
-
buf = "";
|
|
1631
|
-
} else {
|
|
1632
|
-
buf += c;
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1635
|
-
result.push(buf);
|
|
1636
|
-
return result;
|
|
1637
|
-
}
|
|
1638
|
-
function parseDefaultForwarding(forwarding) {
|
|
1639
|
-
const parts = ipv6SafeSplitColon(forwarding);
|
|
1640
|
-
if (parts.length === 3) {
|
|
1641
|
-
const remotePort = parseInt(parts[0], 10);
|
|
1642
|
-
const localDomain = removeIPv6Brackets(parts[1] || "localhost");
|
|
1643
|
-
const localPort = parseInt(parts[2], 10);
|
|
1644
|
-
return { remotePort, localDomain, localPort };
|
|
1645
|
-
}
|
|
1646
|
-
if (parts.length === 4) {
|
|
1647
|
-
const remoteDomain = removeIPv6Brackets(parts[0]);
|
|
1648
|
-
const remotePort = parseInt(parts[1], 10);
|
|
1649
|
-
const localDomain = removeIPv6Brackets(parts[2] || "localhost");
|
|
1650
|
-
const localPort = parseInt(parts[3], 10);
|
|
1651
|
-
return { remoteDomain, remotePort, localDomain, localPort };
|
|
1652
|
-
}
|
|
1653
|
-
return new Error("forwarding address incorrect");
|
|
1654
|
-
}
|
|
1655
|
-
function parseAdditionalForwarding(forwarding) {
|
|
1656
|
-
const toPort = (v) => {
|
|
1657
|
-
if (!v) return null;
|
|
1658
|
-
const n = parseInt(v, 10);
|
|
1659
|
-
return Number.isNaN(n) ? null : n;
|
|
1660
|
-
};
|
|
1661
|
-
const parsed = ipv6SafeSplitColon(forwarding);
|
|
1662
|
-
if (parsed.length !== 4) {
|
|
1663
|
-
return new Error(
|
|
1664
|
-
"forwarding must be in format: [schema//]hostname[/port][@forwardingId]:<placeholder>:<forwardingAddress>:<forwardingPort>"
|
|
1665
|
-
);
|
|
1666
|
-
}
|
|
1667
|
-
const firstPart = parsed[0];
|
|
1668
|
-
const [hostPart] = firstPart.split("@");
|
|
1669
|
-
let protocol = "http";
|
|
1670
|
-
let remoteDomainRaw;
|
|
1671
|
-
let remotePort = 0;
|
|
1672
|
-
if (hostPart.includes("//")) {
|
|
1673
|
-
const [schema, rest] = hostPart.split("//");
|
|
1674
|
-
if (!schema || !VALID_PROTOCOLS.includes(schema)) {
|
|
1675
|
-
return new Error(`invalid protocol: ${schema}`);
|
|
1676
|
-
}
|
|
1677
|
-
protocol = schema;
|
|
1678
|
-
const domainAndPort = rest.split("/");
|
|
1679
|
-
if (domainAndPort.length > 2) {
|
|
1680
|
-
return new Error("invalid forwarding address format");
|
|
1681
|
-
}
|
|
1682
|
-
remoteDomainRaw = domainAndPort[0];
|
|
1683
|
-
if (!remoteDomainRaw || !domainRegex.test(remoteDomainRaw)) {
|
|
1684
|
-
return new Error("invalid remote domain");
|
|
1685
|
-
}
|
|
1686
|
-
const parsedRemotePort = toPort(domainAndPort[1]);
|
|
1687
|
-
if (protocol === "http") {
|
|
1688
|
-
remotePort = 0;
|
|
1689
|
-
} else {
|
|
1690
|
-
if (parsedRemotePort === null || !isValidPort(parsedRemotePort)) {
|
|
1691
|
-
return new Error(
|
|
1692
|
-
`${protocol} forwarding requires port in format ${protocol}//domain/remotePort`
|
|
1693
|
-
);
|
|
1822
|
+
error(code, err, fallback) {
|
|
1823
|
+
return newErrorResponse({
|
|
1824
|
+
code,
|
|
1825
|
+
message: err instanceof Error ? err.message : fallback
|
|
1826
|
+
});
|
|
1694
1827
|
}
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
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
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
}
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
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 [];
|
|
1874
|
+
}
|
|
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");
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
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
|
+
}
|
|
1907
|
+
}
|
|
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
|
+
};
|
|
1790
1962
|
}
|
|
1791
|
-
}
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
logger.
|
|
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;
|
|
1802
1975
|
}
|
|
1976
|
+
return true;
|
|
1977
|
+
} catch (e) {
|
|
1978
|
+
logger.warn("Failed to parse connection status message", { error: String(e) });
|
|
1979
|
+
return true;
|
|
1803
1980
|
}
|
|
1804
|
-
return null;
|
|
1805
|
-
}
|
|
1806
|
-
function isSaveConfOption(values) {
|
|
1807
|
-
const saveconf = values["saveconf"];
|
|
1808
|
-
if (typeof saveconf === "string" && saveconf.trim().length > 0) {
|
|
1809
|
-
return saveconf;
|
|
1810
|
-
}
|
|
1811
|
-
return null;
|
|
1812
|
-
}
|
|
1813
|
-
function parseServe(finalConfig, values) {
|
|
1814
|
-
const sv = values.serve;
|
|
1815
|
-
if (typeof sv !== "string" || sv.trim().length === 0) return null;
|
|
1816
|
-
finalConfig.serve = sv;
|
|
1817
|
-
return null;
|
|
1818
|
-
}
|
|
1819
|
-
async function buildFinalConfig(values, positionals) {
|
|
1820
|
-
let token;
|
|
1821
|
-
let server;
|
|
1822
|
-
let type;
|
|
1823
|
-
let forceFlag = false;
|
|
1824
|
-
let qrCode = false;
|
|
1825
|
-
let finalConfig = new Object();
|
|
1826
|
-
let saveconf = isSaveConfOption(values);
|
|
1827
|
-
const configFromFile = loadJsonConfig(values);
|
|
1828
|
-
const userParse = parseUsers(positionals, values.token);
|
|
1829
|
-
token = userParse.token;
|
|
1830
|
-
server = userParse.server;
|
|
1831
|
-
type = userParse.type;
|
|
1832
|
-
forceFlag = userParse.forceFlag;
|
|
1833
|
-
qrCode = userParse.qrCode;
|
|
1834
|
-
const remainingPositionals = userParse.remaining;
|
|
1835
|
-
const initialTunnel = type || values.type;
|
|
1836
|
-
finalConfig = {
|
|
1837
|
-
...defaultOptions,
|
|
1838
|
-
...configFromFile || {},
|
|
1839
|
-
// Apply loaded config on top of defaults
|
|
1840
|
-
configid: getRandomId(),
|
|
1841
|
-
token: token || (configFromFile?.token || (typeof values.token === "string" ? values.token : "")),
|
|
1842
|
-
serverAddress: server || (configFromFile?.serverAddress || defaultOptions.serverAddress),
|
|
1843
|
-
tunnelType: initialTunnel ? [initialTunnel] : configFromFile?.tunnelType || [import_pinggy3.TunnelType.Http],
|
|
1844
|
-
NoTUI: values.notui || (configFromFile?.NoTUI || false),
|
|
1845
|
-
qrCode: qrCode || (configFromFile?.qrCode || false),
|
|
1846
|
-
autoReconnect: values.autoreconnect || (configFromFile?.autoReconnect || false)
|
|
1847
|
-
};
|
|
1848
|
-
parseType(finalConfig, values, type);
|
|
1849
|
-
parseToken(finalConfig, token || values.token);
|
|
1850
|
-
const dbgErr = parseDebugger(finalConfig, values);
|
|
1851
|
-
if (dbgErr instanceof Error) throw dbgErr;
|
|
1852
|
-
const lpErr = parseLocalPort(finalConfig, values);
|
|
1853
|
-
if (lpErr instanceof Error) throw lpErr;
|
|
1854
|
-
const rErr = parseReverseTunnelAddr(finalConfig, values);
|
|
1855
|
-
if (rErr instanceof Error) throw rErr;
|
|
1856
|
-
const lErr = parseLocalTunnelAddr(finalConfig, values);
|
|
1857
|
-
if (lErr instanceof Error) throw lErr;
|
|
1858
|
-
const serveErr = parseServe(finalConfig, values);
|
|
1859
|
-
if (serveErr instanceof Error) throw serveErr;
|
|
1860
|
-
if (forceFlag) finalConfig.force = true;
|
|
1861
|
-
parseArgs(finalConfig, remainingPositionals);
|
|
1862
|
-
storeJson(finalConfig, saveconf);
|
|
1863
|
-
return finalConfig;
|
|
1864
1981
|
}
|
|
1865
|
-
var
|
|
1866
|
-
var
|
|
1867
|
-
"src/
|
|
1982
|
+
var import_zod2, WebSocketCommandHandler;
|
|
1983
|
+
var init_websocket_handlers = __esm({
|
|
1984
|
+
"src/remote_management/websocket_handlers.ts"() {
|
|
1868
1985
|
"use strict";
|
|
1869
1986
|
init_cjs_shims();
|
|
1870
|
-
init_defaults();
|
|
1871
|
-
init_extendedOptions();
|
|
1872
1987
|
init_logger();
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
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
|
+
};
|
|
1879
2120
|
}
|
|
1880
2121
|
});
|
|
1881
2122
|
|
|
1882
|
-
// src/
|
|
1883
|
-
function
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
if (typeof codeOrError === "object") {
|
|
1888
|
-
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;
|
|
1889
2128
|
}
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
message
|
|
1893
|
-
};
|
|
2129
|
+
const trimmed = baseUrl.replace(/\/$/, "");
|
|
2130
|
+
return `${trimmed}/backend/api/v1/remote-management/connect`;
|
|
1894
2131
|
}
|
|
1895
|
-
function
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
error: false,
|
|
1903
|
-
errorresponse: {}
|
|
1904
|
-
};
|
|
2132
|
+
function extractHostname(u) {
|
|
2133
|
+
try {
|
|
2134
|
+
const url = new URL(u);
|
|
2135
|
+
return url.host;
|
|
2136
|
+
} catch {
|
|
2137
|
+
return u;
|
|
2138
|
+
}
|
|
1905
2139
|
}
|
|
1906
|
-
function
|
|
1907
|
-
return
|
|
1908
|
-
response: new Uint8Array(),
|
|
1909
|
-
requestid: "",
|
|
1910
|
-
command: "",
|
|
1911
|
-
error: true,
|
|
1912
|
-
errorresponse: errorResponse
|
|
1913
|
-
};
|
|
2140
|
+
function sleep(ms) {
|
|
2141
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
1914
2142
|
}
|
|
1915
|
-
function
|
|
1916
|
-
|
|
1917
|
-
if (
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
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
|
+
}
|
|
1923
2154
|
}
|
|
1924
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1925
|
-
return {
|
|
1926
|
-
state: assignedState,
|
|
1927
|
-
errorcode: errorCode,
|
|
1928
|
-
errormsg: errorMsg,
|
|
1929
|
-
createdtimestamp: now,
|
|
1930
|
-
starttimestamp: now,
|
|
1931
|
-
endtimestamp: now,
|
|
1932
|
-
warnings: []
|
|
1933
|
-
};
|
|
1934
|
-
}
|
|
1935
|
-
function newStats() {
|
|
1936
|
-
return {
|
|
1937
|
-
numLiveConnections: 0,
|
|
1938
|
-
numTotalConnections: 0,
|
|
1939
|
-
numTotalReqBytes: 0,
|
|
1940
|
-
numTotalResBytes: 0,
|
|
1941
|
-
numTotalTxBytes: 0,
|
|
1942
|
-
elapsedTime: 0
|
|
1943
|
-
};
|
|
1944
2155
|
}
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
"use strict";
|
|
1949
|
-
init_cjs_shims();
|
|
1950
|
-
ErrorCode = {
|
|
1951
|
-
InvalidRequestMethodError: "INVALID_REQUEST_METHOD",
|
|
1952
|
-
InvalidRequestBodyError: "COULD_NOT_READ_BODY",
|
|
1953
|
-
InternalServerError: "INTERNAL_SERVER_ERROR",
|
|
1954
|
-
InvalidBodyFormatError: "INVALID_DATA_FORMAT",
|
|
1955
|
-
ErrorStartingTunnel: "ERROR_STARTING_TUNNEL",
|
|
1956
|
-
TunnelNotFound: "TUNNEL_WITH_ID_OR_CONFIG_ID_NOT_FOUND",
|
|
1957
|
-
TunnelAlreadyRunningError: "TUNNEL_WITH_ID_OR_CONFIG_ID_ALREADY_RUNNING",
|
|
1958
|
-
WebsocketUpgradeFailError: "WEBSOCKET_UPGRADE_FAILED",
|
|
1959
|
-
RemoteManagementAlreadyRunning: "REMOTE_MANAGEMENT_ALREADY_RUNNING",
|
|
1960
|
-
RemoteManagementNotRunning: "REMOTE_MANAGEMENT_NOT_RUNNING",
|
|
1961
|
-
RemoteManagementDeserializationFailed: "REMOTE_MANAGEMENT_DESERIALIZATION_FAILED"
|
|
1962
|
-
};
|
|
1963
|
-
RemoteManagementStatus = {
|
|
1964
|
-
Connecting: "CONNECTING",
|
|
1965
|
-
Disconnecting: "DISCONNECTING",
|
|
1966
|
-
Reconnecting: "RECONNECTING",
|
|
1967
|
-
Running: "RUNNING",
|
|
1968
|
-
NotRunning: "NOT_RUNNING",
|
|
1969
|
-
Error: "ERROR"
|
|
1970
|
-
};
|
|
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>)");
|
|
1971
2159
|
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
serverAddress: config.serveraddress || "free.pinggy.io",
|
|
1979
|
-
forwarding: `${config.forwardedhost || "localhost"}:${config.localport}`,
|
|
1980
|
-
webDebugger: config.webdebuggerport ? `localhost:${config.webdebuggerport}` : "",
|
|
1981
|
-
ipWhitelist: config.ipwhitelist || [],
|
|
1982
|
-
basicAuth: config.basicauth ? config.basicauth : [],
|
|
1983
|
-
bearerTokenAuth: config.bearerauth ? [config.bearerauth] : [],
|
|
1984
|
-
headerModification: config.headermodification,
|
|
1985
|
-
xForwardedFor: !!config.xff,
|
|
1986
|
-
httpsOnly: config.httpsOnly,
|
|
1987
|
-
originalRequestUrl: config.fullRequestUrl,
|
|
1988
|
-
allowPreflight: config.allowPreflight,
|
|
1989
|
-
reverseProxy: config.noReverseProxy,
|
|
1990
|
-
force: config.force,
|
|
1991
|
-
autoReconnect: config.autoreconnect,
|
|
1992
|
-
optional: {
|
|
1993
|
-
sniServerName: config.localservertlssni || ""
|
|
1994
|
-
}
|
|
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;
|
|
1995
2166
|
};
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
const parsedLocalPort = forwarding.split(":").length == 3 ? parseInt(forwarding.split(":")[2], 10) : parseInt(forwarding.split(":")[1], 10);
|
|
2001
|
-
const tunnelType = (Array.isArray(opts.forwarding) ? opts.forwarding[0]?.type : void 0) ?? import_pinggy4.TunnelType.Http;
|
|
2002
|
-
const parsedTokens = opts.bearerTokenAuth ? Array.isArray(opts.bearerTokenAuth) ? opts.bearerTokenAuth : JSON.parse(opts.bearerTokenAuth) : [];
|
|
2003
|
-
return {
|
|
2004
|
-
allowPreflight: opts.allowPreflight ?? false,
|
|
2005
|
-
allowpreflight: opts.allowPreflight ?? false,
|
|
2006
|
-
autoreconnect: opts.autoReconnect ?? false,
|
|
2007
|
-
basicauth: opts.basicAuth && Object.keys(opts.basicAuth).length ? opts.basicAuth : null,
|
|
2008
|
-
bearerauth: parsedTokens.length ? parsedTokens.join(",") : null,
|
|
2009
|
-
configid,
|
|
2010
|
-
configname: configName,
|
|
2011
|
-
greetmsg: greetMsg || "",
|
|
2012
|
-
force: opts.force ?? false,
|
|
2013
|
-
forwardedhost: parsedForwardedHost || "localhost",
|
|
2014
|
-
fullRequestUrl: opts.originalRequestUrl ?? false,
|
|
2015
|
-
headermodification: opts.headerModification || [],
|
|
2016
|
-
//structured list
|
|
2017
|
-
httpsOnly: opts.httpsOnly ?? false,
|
|
2018
|
-
internalwebdebuggerport: 0,
|
|
2019
|
-
ipwhitelist: opts.ipWhitelist ? Array.isArray(opts.ipWhitelist) ? opts.ipWhitelist : JSON.parse(opts.ipWhitelist) : null,
|
|
2020
|
-
localport: parsedLocalPort || 0,
|
|
2021
|
-
localservertlssni: null,
|
|
2022
|
-
regioncode: "",
|
|
2023
|
-
noReverseProxy: opts.reverseProxy ?? false,
|
|
2024
|
-
serveraddress: opts.serverAddress || "free.pinggy.io",
|
|
2025
|
-
serverport: 0,
|
|
2026
|
-
statusCheckInterval: 0,
|
|
2027
|
-
token: opts.token || "",
|
|
2028
|
-
tunnelTimeout: 0,
|
|
2029
|
-
type: tunnelType,
|
|
2030
|
-
webdebuggerport: Number(opts.webDebugger?.split(":")[0]) || 0,
|
|
2031
|
-
xff: opts.xForwardedFor ? "1" : "",
|
|
2032
|
-
localsservertls: localserverTls || false,
|
|
2033
|
-
additionalForwarding: additionalForwarding || [],
|
|
2034
|
-
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 });
|
|
2035
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();
|
|
2036
2188
|
}
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
init_cjs_shims();
|
|
2042
|
-
import_pinggy4 = require("@pinggy/pinggy");
|
|
2043
|
-
import_zod = require("zod");
|
|
2044
|
-
HeaderModificationSchema = import_zod.z.object({
|
|
2045
|
-
key: import_zod.z.string(),
|
|
2046
|
-
value: import_zod.z.array(import_zod.z.string()).optional(),
|
|
2047
|
-
type: import_zod.z.enum(["add", "remove", "update"])
|
|
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}` }
|
|
2048
2193
|
});
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
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) });
|
|
2222
|
+
}
|
|
2054
2223
|
});
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
configid: import_zod.z.string(),
|
|
2064
|
-
configname: import_zod.z.string(),
|
|
2065
|
-
greetmsg: import_zod.z.string().optional(),
|
|
2066
|
-
force: import_zod.z.boolean(),
|
|
2067
|
-
forwardedhost: import_zod.z.string(),
|
|
2068
|
-
fullRequestUrl: import_zod.z.boolean(),
|
|
2069
|
-
headermodification: import_zod.z.array(HeaderModificationSchema),
|
|
2070
|
-
httpsOnly: import_zod.z.boolean(),
|
|
2071
|
-
internalwebdebuggerport: import_zod.z.number(),
|
|
2072
|
-
ipwhitelist: import_zod.z.array(import_zod.z.string()).nullable(),
|
|
2073
|
-
localport: import_zod.z.number(),
|
|
2074
|
-
localsservertls: import_zod.z.union([import_zod.z.boolean(), import_zod.z.string()]),
|
|
2075
|
-
localservertlssni: import_zod.z.string().nullable(),
|
|
2076
|
-
regioncode: import_zod.z.string(),
|
|
2077
|
-
noReverseProxy: import_zod.z.boolean(),
|
|
2078
|
-
serveraddress: import_zod.z.string(),
|
|
2079
|
-
serverport: import_zod.z.number(),
|
|
2080
|
-
statusCheckInterval: import_zod.z.number(),
|
|
2081
|
-
token: import_zod.z.string(),
|
|
2082
|
-
tunnelTimeout: import_zod.z.number(),
|
|
2083
|
-
type: import_zod.z.enum([
|
|
2084
|
-
import_pinggy4.TunnelType.Http,
|
|
2085
|
-
import_pinggy4.TunnelType.Tcp,
|
|
2086
|
-
import_pinggy4.TunnelType.Udp,
|
|
2087
|
-
import_pinggy4.TunnelType.Tls,
|
|
2088
|
-
import_pinggy4.TunnelType.TlsTcp
|
|
2089
|
-
]),
|
|
2090
|
-
webdebuggerport: import_zod.z.number(),
|
|
2091
|
-
xff: import_zod.z.string(),
|
|
2092
|
-
additionalForwarding: import_zod.z.array(AdditionalForwardingSchema).optional(),
|
|
2093
|
-
serve: import_zod.z.string().optional()
|
|
2094
|
-
}).superRefine((data, ctx) => {
|
|
2095
|
-
if (data.allowPreflight === void 0 && data.allowpreflight === void 0) {
|
|
2096
|
-
ctx.addIssue({
|
|
2097
|
-
code: "custom",
|
|
2098
|
-
message: "Either allowPreflight or allowpreflight is required",
|
|
2099
|
-
path: ["allowPreflight"]
|
|
2100
|
-
});
|
|
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 });
|
|
2101
2232
|
}
|
|
2102
|
-
|
|
2103
|
-
...data,
|
|
2104
|
-
allowPreflight: data.allowPreflight ?? data.allowpreflight,
|
|
2105
|
-
allowpreflight: data.allowPreflight ?? data.allowpreflight
|
|
2106
|
-
}));
|
|
2107
|
-
StartSchema = import_zod.z.object({
|
|
2108
|
-
tunnelID: import_zod.z.string().nullable().optional(),
|
|
2109
|
-
tunnelConfig: TunnelConfigSchema
|
|
2233
|
+
ws.close();
|
|
2110
2234
|
});
|
|
2111
|
-
|
|
2112
|
-
|
|
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();
|
|
2113
2240
|
});
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
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();
|
|
2118
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;
|
|
2119
2301
|
}
|
|
2120
2302
|
});
|
|
2121
2303
|
|
|
2122
|
-
// src/
|
|
2123
|
-
var
|
|
2124
|
-
var
|
|
2125
|
-
"src/
|
|
2304
|
+
// src/cli/options.ts
|
|
2305
|
+
var cliOptions;
|
|
2306
|
+
var init_options = __esm({
|
|
2307
|
+
"src/cli/options.ts"() {
|
|
2126
2308
|
"use strict";
|
|
2127
2309
|
init_cjs_shims();
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
}
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
}
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
}
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
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
|
-
|
|
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;
|
|
2211
2453
|
}
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
const
|
|
2216
|
-
|
|
2217
|
-
|
|
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(", ")}`);
|
|
2218
2462
|
}
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
this.tunnelManager.getTunnelGreetMessage(t.tunnelid)
|
|
2226
|
-
]);
|
|
2227
|
-
const pinggyOptions = status !== "closed" /* Closed */ && status !== "exited" /* Exited */ ? await this.tunnelManager.getTunnelConfig("", t.tunnelid) : t.tunnelConfig;
|
|
2228
|
-
const tunnelConfig = pinggyOptionsToTunnelConfig(pinggyOptions, t.configid, t.tunnelName, tlsInfo, greetMsg, t.additionalForwarding, t.serve);
|
|
2229
|
-
return {
|
|
2230
|
-
tunnelid: t.tunnelid,
|
|
2231
|
-
remoteurls: t.remoteurls,
|
|
2232
|
-
status: this.buildStatus(t.tunnelid, status, "" /* NoError */),
|
|
2233
|
-
stats: rawStats,
|
|
2234
|
-
tunnelconfig: tunnelConfig
|
|
2235
|
-
};
|
|
2236
|
-
})
|
|
2237
|
-
);
|
|
2238
|
-
} catch (err) {
|
|
2239
|
-
return this.error(ErrorCode.InternalServerError, err, "Failed to list tunnels");
|
|
2240
|
-
}
|
|
2241
|
-
}
|
|
2242
|
-
async handleStop(tunnelid) {
|
|
2243
|
-
try {
|
|
2244
|
-
const { configid } = this.tunnelManager.stopTunnel(tunnelid);
|
|
2245
|
-
const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
|
|
2246
|
-
if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
|
|
2247
|
-
return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, configid, managed.tunnelName, managed.additionalForwarding, managed.serve);
|
|
2248
|
-
} catch (err) {
|
|
2249
|
-
return this.error(ErrorCode.TunnelNotFound, err, "Failed to stop tunnel");
|
|
2250
|
-
}
|
|
2251
|
-
}
|
|
2252
|
-
async handleGet(tunnelid) {
|
|
2253
|
-
try {
|
|
2254
|
-
const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
|
|
2255
|
-
if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
|
|
2256
|
-
return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, managed.configid, managed.tunnelName, managed.additionalForwarding, managed.serve);
|
|
2257
|
-
} catch (err) {
|
|
2258
|
-
return this.error(ErrorCode.TunnelNotFound, err, "Failed to get tunnel information");
|
|
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)`);
|
|
2259
2469
|
}
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
return this.error(ErrorCode.TunnelNotFound, err, "Failed to restart tunnel");
|
|
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`);
|
|
2269
2478
|
}
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
const stats = this.tunnelManager.getTunnelStats(tunnelid);
|
|
2280
|
-
if (!stats) {
|
|
2281
|
-
return [newStats()];
|
|
2282
|
-
}
|
|
2283
|
-
return stats;
|
|
2284
|
-
} catch (err) {
|
|
2285
|
-
return this.error(ErrorCode.TunnelNotFound, err, "Failed to get tunnel stats");
|
|
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`);
|
|
2286
2488
|
}
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
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`);
|
|
2296
2498
|
}
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
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`);
|
|
2303
2508
|
}
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
printer_default.warn(`Connection failed: ${msg}`);
|
|
2317
|
-
logger.warn("Remote management connection failed", { error_code: cs.error_code, error_msg: msg });
|
|
2318
|
-
return false;
|
|
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`);
|
|
2516
|
+
}
|
|
2517
|
+
break;
|
|
2518
|
+
default:
|
|
2519
|
+
printer_default.warn(`Unknown extended option "${key}"`);
|
|
2520
|
+
break;
|
|
2319
2521
|
}
|
|
2320
|
-
return true;
|
|
2321
|
-
} catch (e) {
|
|
2322
|
-
logger.warn("Failed to parse connection status message", { error: String(e) });
|
|
2323
|
-
return true;
|
|
2324
2522
|
}
|
|
2325
2523
|
}
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
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"() {
|
|
2329
2550
|
"use strict";
|
|
2330
2551
|
init_cjs_shims();
|
|
2552
|
+
import_net = require("net");
|
|
2331
2553
|
init_logger();
|
|
2332
|
-
init_types();
|
|
2333
|
-
init_handler();
|
|
2334
|
-
init_remote_schema();
|
|
2335
|
-
import_zod2 = __toESM(require("zod"), 1);
|
|
2336
2554
|
init_printer();
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
this.sendResponse(ws, resp);
|
|
2362
|
-
}
|
|
2363
|
-
async handleStartReq(req, raw) {
|
|
2364
|
-
const dc = StartSchema.parse(raw);
|
|
2365
|
-
printer_default.info("Starting tunnel with config name: " + dc.tunnelConfig.configname);
|
|
2366
|
-
const result = await this.tunnelHandler.handleStart(dc.tunnelConfig);
|
|
2367
|
-
return this.wrapResponse(result, req);
|
|
2368
|
-
}
|
|
2369
|
-
async handleStopReq(req, raw) {
|
|
2370
|
-
const dc = StopSchema.parse(raw);
|
|
2371
|
-
printer_default.info("Stopping tunnel with ID: " + dc.tunnelID);
|
|
2372
|
-
const result = await this.tunnelHandler.handleStop(dc.tunnelID);
|
|
2373
|
-
return this.wrapResponse(result, req);
|
|
2374
|
-
}
|
|
2375
|
-
async handleGetReq(req, raw) {
|
|
2376
|
-
const dc = GetSchema.parse(raw);
|
|
2377
|
-
const result = await this.tunnelHandler.handleGet(dc.tunnelID);
|
|
2378
|
-
return this.wrapResponse(result, req);
|
|
2379
|
-
}
|
|
2380
|
-
async handleRestartReq(req, raw) {
|
|
2381
|
-
const dc = RestartSchema.parse(raw);
|
|
2382
|
-
const result = await this.tunnelHandler.handleRestart(dc.tunnelID);
|
|
2383
|
-
return this.wrapResponse(result, req);
|
|
2384
|
-
}
|
|
2385
|
-
async handleUpdateConfigReq(req, raw) {
|
|
2386
|
-
const dc = UpdateConfigSchema.parse(raw);
|
|
2387
|
-
const result = await this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig);
|
|
2388
|
-
return this.wrapResponse(result, req);
|
|
2389
|
-
}
|
|
2390
|
-
async handleListReq(req) {
|
|
2391
|
-
const result = await this.tunnelHandler.handleList();
|
|
2392
|
-
return this.wrapResponse(result, req);
|
|
2393
|
-
}
|
|
2394
|
-
wrapResponse(result, req) {
|
|
2395
|
-
if (isErrorResponse(result)) {
|
|
2396
|
-
const errResp = NewErrorResponseObject(result);
|
|
2397
|
-
errResp.command = req.command;
|
|
2398
|
-
errResp.requestid = req.requestid;
|
|
2399
|
-
return errResp;
|
|
2400
|
-
}
|
|
2401
|
-
const finalResult = JSON.parse(JSON.stringify(result));
|
|
2402
|
-
if (Array.isArray(finalResult)) {
|
|
2403
|
-
finalResult.forEach((item) => {
|
|
2404
|
-
if (item?.tunnelconfig) {
|
|
2405
|
-
delete item.tunnelconfig.allowPreflight;
|
|
2406
|
-
}
|
|
2407
|
-
});
|
|
2408
|
-
} else if (finalResult?.tunnelconfig) {
|
|
2409
|
-
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;
|
|
2410
2579
|
}
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
}
|
|
2426
|
-
case "stop": {
|
|
2427
|
-
response = await this.handleStopReq(req, raw);
|
|
2428
|
-
break;
|
|
2429
|
-
}
|
|
2430
|
-
case "get": {
|
|
2431
|
-
response = await this.handleGetReq(req, raw);
|
|
2432
|
-
break;
|
|
2433
|
-
}
|
|
2434
|
-
case "restart": {
|
|
2435
|
-
response = await this.handleRestartReq(req, raw);
|
|
2436
|
-
break;
|
|
2437
|
-
}
|
|
2438
|
-
case "updateconfig": {
|
|
2439
|
-
response = await this.handleUpdateConfigReq(req, raw);
|
|
2440
|
-
break;
|
|
2441
|
-
}
|
|
2442
|
-
case "list": {
|
|
2443
|
-
response = await this.handleListReq(req);
|
|
2444
|
-
break;
|
|
2445
|
-
}
|
|
2446
|
-
default:
|
|
2447
|
-
if (typeof req.command === "string") {
|
|
2448
|
-
logger.warn("Unknown command", { command: req.command });
|
|
2449
|
-
}
|
|
2450
|
-
return this.sendError(ws, req, "Invalid command");
|
|
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 };
|
|
2586
|
+
}
|
|
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.`);
|
|
2451
2594
|
}
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
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.`);
|
|
2458
2602
|
}
|
|
2459
|
-
|
|
2460
|
-
return this.sendError(ws, req, e?.message || "Internal error");
|
|
2603
|
+
processKeyword2(lowerPart);
|
|
2461
2604
|
}
|
|
2462
2605
|
}
|
|
2463
|
-
}
|
|
2606
|
+
}
|
|
2607
|
+
} else if (domainRegex.test(str)) {
|
|
2608
|
+
server = str;
|
|
2464
2609
|
}
|
|
2465
|
-
}
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
let
|
|
2470
|
-
|
|
2471
|
-
|
|
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.");
|
|
2472
2675
|
}
|
|
2473
|
-
|
|
2474
|
-
return `${trimmed}/backend/api/v1/remote-management/connect`;
|
|
2676
|
+
return null;
|
|
2475
2677
|
}
|
|
2476
|
-
function
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
return url.host;
|
|
2480
|
-
} catch {
|
|
2481
|
-
return u;
|
|
2678
|
+
function removeIPv6Brackets(ip) {
|
|
2679
|
+
if (ip.startsWith("[") && ip.endsWith("]")) {
|
|
2680
|
+
return ip.slice(1, -1);
|
|
2482
2681
|
}
|
|
2682
|
+
return ip;
|
|
2483
2683
|
}
|
|
2484
|
-
function
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
}
|
|
2495
|
-
|
|
2496
|
-
|
|
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;
|
|
2497
2700
|
}
|
|
2498
2701
|
}
|
|
2702
|
+
result.push(buf);
|
|
2703
|
+
return result;
|
|
2499
2704
|
}
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
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 };
|
|
2503
2712
|
}
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
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;
|
|
2510
2727
|
};
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
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
|
|
2515
2783
|
};
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
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
|
+
);
|
|
2523
2808
|
}
|
|
2524
|
-
if (_stopRequested) break;
|
|
2525
|
-
printer_default.warn(`Remote management disconnected. Reconnecting in ${RECONNECT_SLEEP_MS / 1e3} seconds...`);
|
|
2526
|
-
logger.info("Reconnecting to remote management after disconnect");
|
|
2527
|
-
await sleep(RECONNECT_SLEEP_MS);
|
|
2528
2809
|
}
|
|
2529
|
-
|
|
2530
|
-
logger.info("Remote management stopped.");
|
|
2531
|
-
return getRemoteManagementState();
|
|
2810
|
+
return null;
|
|
2532
2811
|
}
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
return;
|
|
2560
|
-
}
|
|
2561
|
-
setRemoteManagementState({ status: RemoteManagementStatus.Running, errorMessage: "" });
|
|
2562
|
-
const req = JSON.parse(data.toString("utf8"));
|
|
2563
|
-
await new WebSocketCommandHandler().handle(ws, req);
|
|
2564
|
-
} catch (e) {
|
|
2565
|
-
logger.warn("Failed handling websocket message", { error: String(e) });
|
|
2566
|
-
}
|
|
2567
|
-
});
|
|
2568
|
-
ws.on("unexpected-response", (_, res) => {
|
|
2569
|
-
setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: `HTTP ${res.statusCode}` });
|
|
2570
|
-
if (res.statusCode === 401) {
|
|
2571
|
-
printer_default.error("Unauthorized. Please enter a valid token.");
|
|
2572
|
-
logger.error("Unauthorized (401) on remote management connect");
|
|
2573
|
-
} else {
|
|
2574
|
-
printer_default.warn(`Unexpected HTTP ${res.statusCode}. Retrying...`);
|
|
2575
|
-
logger.warn("Unexpected HTTP response", { statusCode: res.statusCode });
|
|
2576
|
-
}
|
|
2577
|
-
ws.close();
|
|
2578
|
-
});
|
|
2579
|
-
ws.on("close", (code, reason) => {
|
|
2580
|
-
setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: "" });
|
|
2581
|
-
logger.info("WebSocket closed", { code, reason: reason.toString() });
|
|
2582
|
-
printer_default.warn(`Disconnected (code: ${code}). Retrying...`);
|
|
2583
|
-
cleanup();
|
|
2584
|
-
});
|
|
2585
|
-
ws.on("error", (err) => {
|
|
2586
|
-
setRemoteManagementState({ status: RemoteManagementStatus.Error, errorMessage: err.message });
|
|
2587
|
-
logger.warn("WebSocket error", { error: err.message });
|
|
2588
|
-
printer_default.error(err);
|
|
2589
|
-
cleanup();
|
|
2590
|
-
});
|
|
2591
|
-
});
|
|
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
|
+
}
|
|
2826
|
+
}
|
|
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
|
+
}
|
|
2592
2838
|
}
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
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);
|
|
2603
2856
|
}
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
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);
|
|
2611
2869
|
}
|
|
2612
|
-
} finally {
|
|
2613
|
-
currentWs = null;
|
|
2614
|
-
setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: "" });
|
|
2615
|
-
return getRemoteManagementState();
|
|
2616
2870
|
}
|
|
2871
|
+
return null;
|
|
2617
2872
|
}
|
|
2618
|
-
function
|
|
2619
|
-
|
|
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;
|
|
2620
2879
|
}
|
|
2621
|
-
function
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
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;
|
|
2885
|
+
}
|
|
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
|
|
2625
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;
|
|
2626
2947
|
}
|
|
2627
|
-
var
|
|
2628
|
-
var
|
|
2629
|
-
"src/
|
|
2948
|
+
var import_pinggy5, import_fs3, import_path3, domainRegex, KEYWORDS, VALID_PROTOCOLS;
|
|
2949
|
+
var init_buildConfig = __esm({
|
|
2950
|
+
"src/cli/buildConfig.ts"() {
|
|
2630
2951
|
"use strict";
|
|
2631
2952
|
init_cjs_shims();
|
|
2632
|
-
|
|
2953
|
+
init_defaults();
|
|
2954
|
+
init_extendedOptions();
|
|
2633
2955
|
init_logger();
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
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"];
|
|
2645
2971
|
}
|
|
2646
2972
|
});
|
|
2647
2973
|
|
|
@@ -3444,6 +3770,91 @@ function closeDisconnectModal(screen, manager) {
|
|
|
3444
3770
|
manager.inDisconnectView = false;
|
|
3445
3771
|
screen.render();
|
|
3446
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
|
+
}
|
|
3447
3858
|
function showLoadingModal(screen, modalManager, message = "Loading...") {
|
|
3448
3859
|
if (modalManager.loadingView) return;
|
|
3449
3860
|
modalManager.loadingBox = import_blessed2.default.box({
|
|
@@ -3751,9 +4162,11 @@ var init_TunnelTui = __esm({
|
|
|
3751
4162
|
detailModal: null,
|
|
3752
4163
|
keyBindingsModal: null,
|
|
3753
4164
|
disconnectModal: null,
|
|
4165
|
+
reconnectModal: null,
|
|
3754
4166
|
inDetailView: false,
|
|
3755
4167
|
keyBindingView: false,
|
|
3756
4168
|
inDisconnectView: false,
|
|
4169
|
+
inReconnectView: false,
|
|
3757
4170
|
loadingBox: null,
|
|
3758
4171
|
loadingView: false,
|
|
3759
4172
|
fetchAbortController: null
|
|
@@ -3954,6 +4367,25 @@ Tunnel will be closed.` : info.messages?.join("\n") || "Disconnect request recei
|
|
|
3954
4367
|
);
|
|
3955
4368
|
}
|
|
3956
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
|
+
}
|
|
3957
4389
|
start() {
|
|
3958
4390
|
this.screen.render();
|
|
3959
4391
|
}
|
|
@@ -4054,6 +4486,35 @@ async function startCli(finalConfig, manager) {
|
|
|
4054
4486
|
}
|
|
4055
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"));
|
|
4056
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
|
+
});
|
|
4057
4518
|
manager2.registerDisconnectListener(tunnel.tunnelid, async (tunnelId, error, messages) => {
|
|
4058
4519
|
if (activeTui) {
|
|
4059
4520
|
disconnectState = {
|
|
@@ -4231,6 +4692,19 @@ var init_main = __esm({
|
|
|
4231
4692
|
});
|
|
4232
4693
|
|
|
4233
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);
|
|
4234
4708
|
init_cjs_shims();
|
|
4235
4709
|
|
|
4236
4710
|
// src/utils/detect_vc_redist_on_windows.ts
|
|
@@ -4275,52 +4749,21 @@ function checkRegistry() {
|
|
|
4275
4749
|
}
|
|
4276
4750
|
return false;
|
|
4277
4751
|
}
|
|
4278
|
-
function
|
|
4279
|
-
if (import_os.default.platform() !== "win32") return null;
|
|
4280
|
-
try {
|
|
4281
|
-
for (const key of REGISTRY_KEYS) {
|
|
4282
|
-
const cmd = `reg query "${key}" /v Version 2>nul`;
|
|
4283
|
-
const result = (0, import_child_process.execSync)(cmd, { encoding: "utf8" });
|
|
4284
|
-
const match = result.match(/Version\s+REG_SZ\s+(\S+)/);
|
|
4285
|
-
if (match) {
|
|
4286
|
-
return match[1];
|
|
4287
|
-
}
|
|
4288
|
-
}
|
|
4289
|
-
} catch {
|
|
4290
|
-
}
|
|
4291
|
-
return null;
|
|
4292
|
-
}
|
|
4293
|
-
function hasVCRedist() {
|
|
4294
|
-
if (import_os.default.platform() !== "win32") {
|
|
4295
|
-
return true;
|
|
4296
|
-
}
|
|
4297
|
-
if (checkRegistry()) {
|
|
4298
|
-
return true;
|
|
4299
|
-
}
|
|
4300
|
-
if (checkDLLs()) {
|
|
4301
|
-
return true;
|
|
4302
|
-
}
|
|
4303
|
-
return false;
|
|
4304
|
-
}
|
|
4305
|
-
function getVCRedistStatus() {
|
|
4752
|
+
function checkVCRedist() {
|
|
4306
4753
|
if (import_os.default.platform() !== "win32") {
|
|
4307
4754
|
return {
|
|
4308
4755
|
required: false,
|
|
4309
4756
|
installed: true,
|
|
4310
|
-
|
|
4311
|
-
method: "non-windows"
|
|
4757
|
+
message: null
|
|
4312
4758
|
};
|
|
4313
4759
|
}
|
|
4314
4760
|
const registryInstalled = checkRegistry();
|
|
4315
4761
|
const dllsPresent = checkDLLs();
|
|
4316
|
-
const
|
|
4762
|
+
const installed = registryInstalled || dllsPresent;
|
|
4317
4763
|
return {
|
|
4318
4764
|
required: true,
|
|
4319
|
-
installed
|
|
4320
|
-
|
|
4321
|
-
registryCheck: registryInstalled,
|
|
4322
|
-
dllCheck: dllsPresent,
|
|
4323
|
-
method: registryInstalled ? "registry" : dllsPresent ? "dll" : "none"
|
|
4765
|
+
installed,
|
|
4766
|
+
message: installed ? null : "Missing Microsoft Visual C++ Runtime. This application requires the Microsoft Visual C++ Runtime to run on Windows.\n"
|
|
4324
4767
|
};
|
|
4325
4768
|
}
|
|
4326
4769
|
async function openDownloadPage() {
|
|
@@ -4343,27 +4786,24 @@ async function openDownloadPage() {
|
|
|
4343
4786
|
printer_default.info(url + "\n");
|
|
4344
4787
|
}
|
|
4345
4788
|
}
|
|
4346
|
-
function getVCRedistMessage() {
|
|
4347
|
-
const status = getVCRedistStatus();
|
|
4348
|
-
if (!status.required || status.installed) {
|
|
4349
|
-
return null;
|
|
4350
|
-
}
|
|
4351
|
-
return {
|
|
4352
|
-
error: true,
|
|
4353
|
-
message: "Missing Microsoft Visual C++ Runtime. This application requires the Microsoft Visual C++ Runtime to run on Windows.\nPlease download and install it using the link below, then restart this application.\n"
|
|
4354
|
-
};
|
|
4355
|
-
}
|
|
4356
4789
|
|
|
4357
4790
|
// src/index.ts
|
|
4358
4791
|
init_printer();
|
|
4792
|
+
init_TunnelManager();
|
|
4793
|
+
init_handler();
|
|
4794
|
+
init_logger();
|
|
4795
|
+
init_remoteManagement();
|
|
4796
|
+
init_types();
|
|
4359
4797
|
async function verifyAndLoad() {
|
|
4360
|
-
if (process.platform === "win32"
|
|
4361
|
-
const
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4798
|
+
if (process.platform === "win32") {
|
|
4799
|
+
const vcRedist = checkVCRedist();
|
|
4800
|
+
if (!vcRedist.installed) {
|
|
4801
|
+
printer_default.warn(
|
|
4802
|
+
vcRedist.message ?? "This application requires the Microsoft Visual C++ Runtime on Windows."
|
|
4803
|
+
);
|
|
4804
|
+
await openDownloadPage();
|
|
4805
|
+
process.exit(1);
|
|
4806
|
+
}
|
|
4367
4807
|
}
|
|
4368
4808
|
await Promise.resolve().then(() => (init_main(), main_exports));
|
|
4369
4809
|
}
|
|
@@ -4371,3 +4811,15 @@ verifyAndLoad().catch((err) => {
|
|
|
4371
4811
|
printer_default.error(`Failed to start CLI:, ${err}`);
|
|
4372
4812
|
process.exit(1);
|
|
4373
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
|
+
});
|