pinggy 0.5.3 → 0.5.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/ARCHITECTURE.md +596 -0
- package/README.md +4 -0
- package/dist/{TunnelManager-OPUMAZFX.js → TunnelManager-ZSVX7U3Z.js} +2 -2
- package/dist/{TunnelTui-QZEWWH2H.js → TunnelTui-S35BNVPO.js} +85 -16
- package/dist/{chunk-GK2PQ7KX.js → chunk-HN4YLM7U.js} +227 -74
- package/dist/{chunk-BBPR2LJB.js → chunk-OH6FSLYF.js} +39 -9
- package/dist/{chunk-7G6SJEEA.js → chunk-P7WNBN7F.js} +1 -1
- package/dist/{chunk-DLNUDW6G.js → chunk-PQSLSIDA.js} +67 -13
- package/dist/{chunk-5YHD6M35.js → chunk-YTJEV27N.js} +2 -2
- package/dist/{chunk-WM7I57AA.js → chunk-YWXADADB.js} +1 -1
- package/dist/{configStore-HP3NXPH4.js → configStore-2AZRILVC.js} +2 -2
- package/dist/{daemonChild-WIRWKBKB.js → daemonChild-3ONQO2XI.js} +6 -6
- package/dist/index.cjs +420 -114
- package/dist/index.d.cts +24 -17
- package/dist/index.d.ts +24 -17
- package/dist/index.js +6 -6
- package/dist/{ipcClient-Z2RXDQUQ.js → ipcClient-XRL2WUUM.js} +1 -1
- package/dist/{main-B2EBHFN2.js → main-WXFARRMN.js} +6 -6
- package/dist/workers/file_serve_worker.js +1 -1
- package/jest.config.cjs +1 -0
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -51,7 +51,7 @@ function startSpinner(name = "dots", text = "Loading") {
|
|
|
51
51
|
}
|
|
52
52
|
currentTimer = setInterval(() => {
|
|
53
53
|
const frame = spinner.frames[i = ++i % spinner.frames.length];
|
|
54
|
-
process.stdout.write(`\r${import_picocolors.default.cyan(frame)} ${text}`);
|
|
54
|
+
process.stdout.write(`\r${import_picocolors.default.cyan(frame)} ${text}\x1B[K`);
|
|
55
55
|
}, spinner.interval);
|
|
56
56
|
return () => stopSpinner();
|
|
57
57
|
}
|
|
@@ -67,7 +67,7 @@ function stopSpinnerSuccess(message) {
|
|
|
67
67
|
clearInterval(currentTimer);
|
|
68
68
|
currentTimer = null;
|
|
69
69
|
const finalMessage = message || currentText;
|
|
70
|
-
process.stdout.write(`\r${import_picocolors.default.green("\u2714")} ${finalMessage}
|
|
70
|
+
process.stdout.write(`\r${import_picocolors.default.green("\u2714")} ${finalMessage}\x1B[K
|
|
71
71
|
`);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
@@ -76,7 +76,7 @@ function stopSpinnerFail(message) {
|
|
|
76
76
|
clearInterval(currentTimer);
|
|
77
77
|
currentTimer = null;
|
|
78
78
|
const finalMessage = message || currentText;
|
|
79
|
-
process.stdout.write(`\r${import_picocolors.default.red("\u2716")} ${finalMessage}
|
|
79
|
+
process.stdout.write(`\r${import_picocolors.default.red("\u2716")} ${finalMessage}\x1B[K
|
|
80
80
|
`);
|
|
81
81
|
}
|
|
82
82
|
}
|
|
@@ -1063,6 +1063,29 @@ var init_TunnelManager = __esm({
|
|
|
1063
1063
|
}
|
|
1064
1064
|
return Promise.reject(new Error(`Either configId or tunnelId must be provided`));
|
|
1065
1065
|
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Stop a tunnel instance and wait for the stop to settle, capped by a
|
|
1068
|
+
* timeout. Failure is logged, not thrown: the caller replaces the
|
|
1069
|
+
* instance either way and a hung stop must not block that forever.
|
|
1070
|
+
*/
|
|
1071
|
+
async stopInstanceGuarded(managed, timeoutMs = 5e3) {
|
|
1072
|
+
let timer;
|
|
1073
|
+
try {
|
|
1074
|
+
await Promise.race([
|
|
1075
|
+
managed.instance.stop(),
|
|
1076
|
+
new Promise((_, reject) => {
|
|
1077
|
+
timer = setTimeout(() => reject(new Error(`Stop timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
1078
|
+
})
|
|
1079
|
+
]);
|
|
1080
|
+
} catch (e) {
|
|
1081
|
+
logger.warn("Failed to stop tunnel instance before replacement", {
|
|
1082
|
+
tunnelId: managed.tunnelid,
|
|
1083
|
+
error: errorMessage(e)
|
|
1084
|
+
});
|
|
1085
|
+
} finally {
|
|
1086
|
+
if (timer) clearTimeout(timer);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1066
1089
|
/**
|
|
1067
1090
|
* Restarts a tunnel with its current configuration.
|
|
1068
1091
|
* This function will stop the tunnel if it's running and start it again.
|
|
@@ -1084,6 +1107,9 @@ var init_TunnelManager = __esm({
|
|
|
1084
1107
|
const currentServe = existingTunnel.serve;
|
|
1085
1108
|
const autoReconnect = existingTunnel.autoReconnect || false;
|
|
1086
1109
|
const currentOrigin = existingTunnel.origin;
|
|
1110
|
+
if (!existingTunnel.isStopped) {
|
|
1111
|
+
await this.stopInstanceGuarded(existingTunnel);
|
|
1112
|
+
}
|
|
1087
1113
|
this.tunnelsByTunnelId.delete(tunnelid);
|
|
1088
1114
|
this.tunnelsByConfigId.delete(existingTunnel.configId);
|
|
1089
1115
|
this.tunnelStats.delete(tunnelid);
|
|
@@ -1118,9 +1144,26 @@ var init_TunnelManager = __esm({
|
|
|
1118
1144
|
throw new Error(`Failed to restart tunnel: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1119
1145
|
}
|
|
1120
1146
|
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Carry lifecycle state from a replaced tunnel over to its replacement:
|
|
1149
|
+
* keep createdAt, start the replacement if the old tunnel was running,
|
|
1150
|
+
* otherwise keep it stopped with the original timestamps.
|
|
1151
|
+
*/
|
|
1152
|
+
async applyPreservedLifecycle(tunnel, prior) {
|
|
1153
|
+
if (prior.createdAt) {
|
|
1154
|
+
tunnel.createdAt = prior.createdAt;
|
|
1155
|
+
}
|
|
1156
|
+
if (!prior.isStopped) {
|
|
1157
|
+
await this.startTunnel(tunnel.tunnelid);
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
tunnel.isStopped = true;
|
|
1161
|
+
tunnel.startedAt = prior.startedAt;
|
|
1162
|
+
tunnel.stoppedAt = prior.stoppedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1163
|
+
}
|
|
1121
1164
|
/**
|
|
1122
1165
|
* Updates the configuration of an existing tunnel.
|
|
1123
|
-
*
|
|
1166
|
+
*
|
|
1124
1167
|
* This method handles the process of updating a tunnel's configuration while preserving
|
|
1125
1168
|
* its state. If the tunnel is running, it will be stopped, updated, and restarted.
|
|
1126
1169
|
* In case of failure, it attempts to restore the original configuration.
|
|
@@ -1147,10 +1190,13 @@ var init_TunnelManager = __esm({
|
|
|
1147
1190
|
const currentServe = existingTunnel.serve;
|
|
1148
1191
|
const currentAutoReconnect = existingTunnel.autoReconnect || false;
|
|
1149
1192
|
const currentOrigin = existingTunnel.origin;
|
|
1193
|
+
const currentCreatedAt = existingTunnel.createdAt;
|
|
1194
|
+
const currentStartedAt = existingTunnel.startedAt;
|
|
1195
|
+
const currentStoppedAt = existingTunnel.stoppedAt;
|
|
1150
1196
|
const requestedServe = this.resolveServePath(newConfig);
|
|
1151
1197
|
try {
|
|
1152
1198
|
if (!isStopped) {
|
|
1153
|
-
|
|
1199
|
+
await this.stopInstanceGuarded(existingTunnel);
|
|
1154
1200
|
}
|
|
1155
1201
|
this.tunnelsByTunnelId.delete(currentTunnelId);
|
|
1156
1202
|
this.tunnelsByConfigId.delete(currentTunnelConfigId);
|
|
@@ -1162,7 +1208,6 @@ var init_TunnelManager = __esm({
|
|
|
1162
1208
|
};
|
|
1163
1209
|
const effectiveServe = requestedServe !== void 0 ? requestedServe : currentServe;
|
|
1164
1210
|
const effectiveTunnelName = newTunnelName !== void 0 ? newTunnelName : currentTunnelName;
|
|
1165
|
-
let configWithForwarding;
|
|
1166
1211
|
const newTunnel = await this._createTunnelWithProcessedConfig({
|
|
1167
1212
|
configId,
|
|
1168
1213
|
tunnelid: currentTunnelId,
|
|
@@ -1172,9 +1217,12 @@ var init_TunnelManager = __esm({
|
|
|
1172
1217
|
serve: effectiveServe,
|
|
1173
1218
|
autoReconnect: currentAutoReconnect
|
|
1174
1219
|
});
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1220
|
+
await this.applyPreservedLifecycle(newTunnel, {
|
|
1221
|
+
isStopped,
|
|
1222
|
+
createdAt: currentCreatedAt,
|
|
1223
|
+
startedAt: currentStartedAt,
|
|
1224
|
+
stoppedAt: currentStoppedAt
|
|
1225
|
+
});
|
|
1178
1226
|
logger.info("Tunnel configuration updated", {
|
|
1179
1227
|
tunnelId: newTunnel.tunnelid,
|
|
1180
1228
|
configId: newTunnel.configId,
|
|
@@ -1196,9 +1244,12 @@ var init_TunnelManager = __esm({
|
|
|
1196
1244
|
serve: currentServe,
|
|
1197
1245
|
autoReconnect: currentAutoReconnect
|
|
1198
1246
|
});
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1247
|
+
await this.applyPreservedLifecycle(originalTunnel, {
|
|
1248
|
+
isStopped,
|
|
1249
|
+
createdAt: currentCreatedAt,
|
|
1250
|
+
startedAt: currentStartedAt,
|
|
1251
|
+
stoppedAt: currentStoppedAt
|
|
1252
|
+
});
|
|
1202
1253
|
logger.warn("Restored original tunnel configuration after update failure", {
|
|
1203
1254
|
currentTunnelId,
|
|
1204
1255
|
error: errorMessage(error)
|
|
@@ -1815,6 +1866,9 @@ var init_TunnelManager = __esm({
|
|
|
1815
1866
|
try {
|
|
1816
1867
|
logger.info("Tunnel reconnection completed", { tunnelId, urls });
|
|
1817
1868
|
const managedTunnel = this.tunnelsByTunnelId.get(tunnelId);
|
|
1869
|
+
if (managedTunnel?.isStopping || managedTunnel?.isStopped) {
|
|
1870
|
+
return;
|
|
1871
|
+
}
|
|
1818
1872
|
if (managedTunnel) {
|
|
1819
1873
|
managedTunnel.isStopped = false;
|
|
1820
1874
|
managedTunnel.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -2152,6 +2206,7 @@ function pinggyOptionsToTunnelConfigV1(opts, configStoredInCli) {
|
|
|
2152
2206
|
httpsOnly: opts.httpsOnly ?? false,
|
|
2153
2207
|
originalRequestUrl: opts.originalRequestUrl ?? false,
|
|
2154
2208
|
allowPreflight: opts.allowPreflight ?? false,
|
|
2209
|
+
haProxy: opts.haProxy,
|
|
2155
2210
|
optional: opts.optional || {}
|
|
2156
2211
|
};
|
|
2157
2212
|
}
|
|
@@ -2341,7 +2396,7 @@ var init_remote_schema = __esm({
|
|
|
2341
2396
|
ForwardingEntryV2Schema = import_zod.z.object({
|
|
2342
2397
|
listenAddress: import_zod.z.string().optional(),
|
|
2343
2398
|
address: import_zod.z.string(),
|
|
2344
|
-
type: import_zod.z.enum([import_pinggy3.TunnelType.Http, import_pinggy3.TunnelType.Tcp, import_pinggy3.TunnelType.Udp, import_pinggy3.TunnelType.Tls, import_pinggy3.TunnelType.TlsTcp])
|
|
2399
|
+
type: import_zod.z.enum([import_pinggy3.TunnelType.Http, import_pinggy3.TunnelType.Tcp, import_pinggy3.TunnelType.Udp, import_pinggy3.TunnelType.Tls, import_pinggy3.TunnelType.TlsTcp]).default(import_pinggy3.TunnelType.Http)
|
|
2345
2400
|
});
|
|
2346
2401
|
OptionalSchema = import_zod.z.object({
|
|
2347
2402
|
sniServerName: import_zod.z.string().optional(),
|
|
@@ -2385,7 +2440,22 @@ var init_remote_schema = __esm({
|
|
|
2385
2440
|
httpsOnly: import_zod.z.boolean().optional(),
|
|
2386
2441
|
originalRequestUrl: import_zod.z.boolean().optional(),
|
|
2387
2442
|
allowPreflight: import_zod.z.boolean().optional(),
|
|
2388
|
-
|
|
2443
|
+
haProxy: import_zod.z.enum([import_pinggy3.HaProxyVersion.V1, import_pinggy3.HaProxyVersion.V2]).optional(),
|
|
2444
|
+
serve: import_zod.z.string().optional(),
|
|
2445
|
+
optional: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
2446
|
+
}).superRefine((config, ctx) => {
|
|
2447
|
+
if (!config.haProxy) {
|
|
2448
|
+
return;
|
|
2449
|
+
}
|
|
2450
|
+
const entries = Array.isArray(config.forwarding) ? config.forwarding : [];
|
|
2451
|
+
const primaryType = entries.length > 0 ? entries[0].type : void 0;
|
|
2452
|
+
if (primaryType !== import_pinggy3.TunnelType.Tcp) {
|
|
2453
|
+
ctx.addIssue({
|
|
2454
|
+
code: "custom",
|
|
2455
|
+
path: ["haProxy"],
|
|
2456
|
+
message: `haProxy can only be used with "tcp" tunnels, but the first forwarding rule has type: ${primaryType ?? import_pinggy3.TunnelType.Http}`
|
|
2457
|
+
});
|
|
2458
|
+
}
|
|
2389
2459
|
});
|
|
2390
2460
|
TUNNEL_CONFIG_V1_KEYS = Object.keys(
|
|
2391
2461
|
TunnelConfigV1Schema.shape
|
|
@@ -2525,11 +2595,11 @@ var init_handler = __esm({
|
|
|
2525
2595
|
startPromise.catch((err) => {
|
|
2526
2596
|
logger.error("No-wait startTunnel failed", { tunnelid, err: String(err) });
|
|
2527
2597
|
});
|
|
2528
|
-
return this.buildPendingTunnelResponseV2(tunnelid, tunnelConfig, config, config.configId, config.name, config.
|
|
2598
|
+
return this.buildPendingTunnelResponseV2(tunnelid, tunnelConfig, config, config.configId, config.name, config.serve);
|
|
2529
2599
|
}
|
|
2530
2600
|
await startPromise;
|
|
2531
2601
|
const tunnelPconfig = await this.tunnelManager.getTunnelConfig("", tunnelid);
|
|
2532
|
-
return this.buildTunnelResponseV2(tunnelid, tunnelPconfig, config, config.configId, config.name, config.
|
|
2602
|
+
return this.buildTunnelResponseV2(tunnelid, tunnelPconfig, config, config.configId, config.name, config.serve);
|
|
2533
2603
|
} catch (err) {
|
|
2534
2604
|
if (err instanceof TunnelAlreadyRunningError) {
|
|
2535
2605
|
return this.error(ErrorCode.TunnelAlreadyRunningError, err, err.message);
|
|
@@ -2551,10 +2621,12 @@ var init_handler = __esm({
|
|
|
2551
2621
|
if (noWait) {
|
|
2552
2622
|
const existing = this.tunnelManager.getManagedTunnel(config.configid);
|
|
2553
2623
|
if (!existing.tunnelConfig) throw new Error("Invalid tunnel state before configuration update");
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2624
|
+
if (!existing.isStopped) {
|
|
2625
|
+
this.tunnelManager.updateConfig(updateOpts).catch((err) => {
|
|
2626
|
+
logger.error("No-wait updateConfig failed", { configid: config.configid, err: String(err) });
|
|
2627
|
+
});
|
|
2628
|
+
return this.buildPendingTunnelResponse(existing.tunnelid, existing.tunnelConfig, config.configid, existing.tunnelName, existing.serve);
|
|
2629
|
+
}
|
|
2558
2630
|
}
|
|
2559
2631
|
const tunnel = await this.tunnelManager.updateConfig(updateOpts);
|
|
2560
2632
|
if (!tunnel.instance || !tunnel.tunnelConfig)
|
|
@@ -2569,10 +2641,12 @@ var init_handler = __esm({
|
|
|
2569
2641
|
if (noWait) {
|
|
2570
2642
|
const existing = this.tunnelManager.getManagedTunnel(config.configId);
|
|
2571
2643
|
if (!existing.tunnelConfig) throw new Error("Invalid tunnel state before configuration update");
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2644
|
+
if (!existing.isStopped) {
|
|
2645
|
+
this.tunnelManager.updateConfig(config).catch((err) => {
|
|
2646
|
+
logger.error("No-wait updateConfigV2 failed", { configId: config.configId, err: String(err) });
|
|
2647
|
+
});
|
|
2648
|
+
return this.buildPendingTunnelResponseV2(existing.tunnelid, existing.tunnelConfig, config, config.configId, existing.tunnelName, existing.serve);
|
|
2649
|
+
}
|
|
2576
2650
|
}
|
|
2577
2651
|
const tunnel = await this.tunnelManager.updateConfig(config);
|
|
2578
2652
|
if (!tunnel.instance || !tunnel.tunnelConfig)
|
|
@@ -2795,8 +2869,8 @@ var init_ipcClient = __esm({
|
|
|
2795
2869
|
async stopTunnel(tunnelid) {
|
|
2796
2870
|
return this.call(Route.StopTunnel, { tunnelid });
|
|
2797
2871
|
}
|
|
2798
|
-
async restartTunnel(tunnelid) {
|
|
2799
|
-
return this.call(Route.RestartTunnel, { tunnelid });
|
|
2872
|
+
async restartTunnel(tunnelid, noWait) {
|
|
2873
|
+
return this.call(Route.RestartTunnel, { tunnelid, noWait });
|
|
2800
2874
|
}
|
|
2801
2875
|
// v1 operations (used by remote management via daemon)
|
|
2802
2876
|
async startTunnelV1(config, mode, noWait) {
|
|
@@ -2978,6 +3052,7 @@ function printHelpMessage() {
|
|
|
2978
3052
|
console.log(" x:noreverseproxy Disable built-in reverse-proxy header injection");
|
|
2979
3053
|
console.log(" x:localservertls:host Connect to local HTTPS server with SNI");
|
|
2980
3054
|
console.log(" x:passpreflight Pass CORS preflight requests unchanged");
|
|
3055
|
+
console.log(" x:haproxy[:v1|:v2] Enable HAProxy PROXY protocol, TCP tunnels only (default v1)");
|
|
2981
3056
|
console.log(" a:Key:Val Add header");
|
|
2982
3057
|
console.log(" u:Key:Val Update header");
|
|
2983
3058
|
console.log(" r:Key Remove header");
|
|
@@ -3103,7 +3178,7 @@ var init_parseArgs = __esm({
|
|
|
3103
3178
|
});
|
|
3104
3179
|
|
|
3105
3180
|
// src/remote_management/websocket_printer.ts
|
|
3106
|
-
var import_picocolors3,
|
|
3181
|
+
var import_picocolors3, PENDING_TIMEOUT_MS, PendingTracker, RemoteManagementWebSocketPrinter, remoteManagementWebSocketPrinter;
|
|
3107
3182
|
var init_websocket_printer = __esm({
|
|
3108
3183
|
"src/remote_management/websocket_printer.ts"() {
|
|
3109
3184
|
"use strict";
|
|
@@ -3112,13 +3187,71 @@ var init_websocket_printer = __esm({
|
|
|
3112
3187
|
init_types();
|
|
3113
3188
|
init_printer();
|
|
3114
3189
|
import_picocolors3 = __toESM(require("picocolors"), 1);
|
|
3115
|
-
|
|
3116
|
-
|
|
3190
|
+
PENDING_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3191
|
+
PendingTracker = class {
|
|
3117
3192
|
constructor() {
|
|
3118
|
-
this.
|
|
3193
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
3194
|
+
}
|
|
3195
|
+
queue(entry) {
|
|
3196
|
+
this.latestConfigId = entry.configId;
|
|
3197
|
+
this.entries.set(entry.configId, entry);
|
|
3198
|
+
}
|
|
3199
|
+
/** Store an entry without claiming the latest cursor. */
|
|
3200
|
+
upsert(entry) {
|
|
3201
|
+
this.entries.set(entry.configId, entry);
|
|
3202
|
+
}
|
|
3203
|
+
get(configId) {
|
|
3204
|
+
return this.entries.get(configId);
|
|
3205
|
+
}
|
|
3206
|
+
find(configId, tunnelId) {
|
|
3207
|
+
const byConfigId = this.entries.get(configId);
|
|
3208
|
+
if (byConfigId) {
|
|
3209
|
+
return byConfigId;
|
|
3210
|
+
}
|
|
3211
|
+
if (!tunnelId) {
|
|
3212
|
+
return void 0;
|
|
3213
|
+
}
|
|
3214
|
+
for (const entry of this.entries.values()) {
|
|
3215
|
+
if (entry.tunnelId === tunnelId) {
|
|
3216
|
+
return entry;
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
return void 0;
|
|
3220
|
+
}
|
|
3221
|
+
delete(configId) {
|
|
3222
|
+
this.entries.delete(configId);
|
|
3223
|
+
if (this.latestConfigId === configId) {
|
|
3224
|
+
this.latestConfigId = void 0;
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
get latest() {
|
|
3228
|
+
return this.latestConfigId;
|
|
3229
|
+
}
|
|
3230
|
+
get hasLatest() {
|
|
3231
|
+
return this.entries.size > 0 && this.latestConfigId !== void 0;
|
|
3232
|
+
}
|
|
3233
|
+
isLatest(configId) {
|
|
3234
|
+
return configId === this.latestConfigId;
|
|
3119
3235
|
}
|
|
3120
|
-
|
|
3121
|
-
|
|
3236
|
+
cleanupExpired(onTimeout) {
|
|
3237
|
+
const now = Date.now();
|
|
3238
|
+
for (const [configId, entry] of this.entries) {
|
|
3239
|
+
if (now - entry.queuedAt <= PENDING_TIMEOUT_MS) {
|
|
3240
|
+
continue;
|
|
3241
|
+
}
|
|
3242
|
+
const wasLatest = this.isLatest(configId);
|
|
3243
|
+
this.delete(configId);
|
|
3244
|
+
onTimeout(entry, wasLatest);
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
};
|
|
3248
|
+
RemoteManagementWebSocketPrinter = class {
|
|
3249
|
+
constructor() {
|
|
3250
|
+
this.pendingStarts = new PendingTracker();
|
|
3251
|
+
this.pendingActions = new PendingTracker();
|
|
3252
|
+
// tunnelId -> details, fed by monitorList. Stop/restart requests only carry
|
|
3253
|
+
// a tunnelId; without this the spinner shows the raw id.
|
|
3254
|
+
this.tunnelDetailsCache = /* @__PURE__ */ new Map();
|
|
3122
3255
|
}
|
|
3123
3256
|
queueStart(config) {
|
|
3124
3257
|
this.cleanupExpiredPendingStarts();
|
|
@@ -3127,24 +3260,23 @@ var init_websocket_printer = __esm({
|
|
|
3127
3260
|
configName: this.getConfigNameFromRequest(config),
|
|
3128
3261
|
queuedAt: Date.now()
|
|
3129
3262
|
};
|
|
3130
|
-
this.
|
|
3131
|
-
this.pendingStarts.set(entry.configId, entry);
|
|
3263
|
+
this.pendingStarts.queue(entry);
|
|
3132
3264
|
printer_default.startSpinner("Starting tunnel with config name: " + entry.configName);
|
|
3133
3265
|
}
|
|
3134
3266
|
failQueuedStart(config, reason) {
|
|
3135
3267
|
const configId = this.getConfigIdFromRequest(config);
|
|
3136
3268
|
const pending = this.pendingStarts.get(configId);
|
|
3137
3269
|
const configName = pending?.configName || this.getConfigNameFromRequest(config);
|
|
3270
|
+
const wasLatest = this.pendingStarts.isLatest(configId);
|
|
3138
3271
|
this.pendingStarts.delete(configId);
|
|
3139
|
-
if (
|
|
3140
|
-
this.latestPendingConfigId = void 0;
|
|
3272
|
+
if (wasLatest) {
|
|
3141
3273
|
printer_default.stopSpinnerFail(`Failed to start tunnel with config name: ${configName}. ${reason}`);
|
|
3142
3274
|
}
|
|
3143
3275
|
}
|
|
3144
3276
|
handleStartResult(config, result) {
|
|
3145
3277
|
this.cleanupExpiredPendingStarts();
|
|
3146
3278
|
const requestedConfigId = this.getConfigIdFromRequest(config);
|
|
3147
|
-
if (this.
|
|
3279
|
+
if (this.pendingStarts.latest && !this.pendingStarts.isLatest(requestedConfigId)) {
|
|
3148
3280
|
this.pendingStarts.delete(requestedConfigId);
|
|
3149
3281
|
return;
|
|
3150
3282
|
}
|
|
@@ -3152,20 +3284,20 @@ var init_websocket_printer = __esm({
|
|
|
3152
3284
|
this.failQueuedStart(config, result.message);
|
|
3153
3285
|
return;
|
|
3154
3286
|
}
|
|
3155
|
-
const configId = this.getConfigIdFromTunnel(result);
|
|
3156
3287
|
const pending = this.pendingStarts.get(requestedConfigId) || {
|
|
3157
3288
|
configId: requestedConfigId,
|
|
3158
3289
|
configName: this.getConfigNameFromRequest(config),
|
|
3159
3290
|
queuedAt: Date.now()
|
|
3160
3291
|
};
|
|
3161
3292
|
pending.tunnelId = result.tunnelid;
|
|
3162
|
-
this.pendingStarts.
|
|
3293
|
+
this.pendingStarts.upsert(pending);
|
|
3163
3294
|
if (result.remoteurls.length > 0) {
|
|
3164
3295
|
this.completePendingStart(pending, result.remoteurls);
|
|
3165
3296
|
}
|
|
3166
3297
|
}
|
|
3167
3298
|
printStopRequested(tunnelId) {
|
|
3168
3299
|
const details = this.resolveTunnelDetails(tunnelId);
|
|
3300
|
+
this.clearPendingAction(details.configId, tunnelId);
|
|
3169
3301
|
printer_default.startSpinner("Stopping tunnel with config name: " + details.configName);
|
|
3170
3302
|
}
|
|
3171
3303
|
handleStopResult(tunnelId, result) {
|
|
@@ -3179,6 +3311,7 @@ var init_websocket_printer = __esm({
|
|
|
3179
3311
|
}
|
|
3180
3312
|
printRestartRequested(tunnelId) {
|
|
3181
3313
|
const details = this.resolveTunnelDetails(tunnelId);
|
|
3314
|
+
this.clearPendingAction(details.configId, tunnelId);
|
|
3182
3315
|
printer_default.startSpinner("Restarting tunnel with config name: " + details.configName);
|
|
3183
3316
|
}
|
|
3184
3317
|
handleRestartResult(tunnelId, result) {
|
|
@@ -3188,48 +3321,101 @@ var init_websocket_printer = __esm({
|
|
|
3188
3321
|
printer_default.stopSpinnerFail("Failed to restart tunnel with config name: " + details.configName);
|
|
3189
3322
|
return;
|
|
3190
3323
|
}
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3324
|
+
this.queuePendingAction("restart", details.configId, details.configName, result.tunnelid);
|
|
3325
|
+
}
|
|
3326
|
+
printUpdateConfigRequested(config) {
|
|
3327
|
+
const configName = this.getConfigNameFromRequest(config);
|
|
3328
|
+
this.clearPendingAction(this.getConfigIdFromRequest(config));
|
|
3329
|
+
printer_default.startSpinner("Updating tunnel configuration for: " + configName);
|
|
3330
|
+
}
|
|
3331
|
+
handleUpdateConfigResult(config, result) {
|
|
3332
|
+
const configId = this.getConfigIdFromRequest(config);
|
|
3333
|
+
const configName = this.getConfigNameFromRequest(config);
|
|
3334
|
+
if (isErrorResponse(result)) {
|
|
3335
|
+
printer_default.stopSpinnerFail(`Failed to update tunnel configuration for: ${configName}. ${result.message}`);
|
|
3336
|
+
return;
|
|
3337
|
+
}
|
|
3338
|
+
if (result.status.state === "exited" /* Exited */) {
|
|
3339
|
+
printer_default.stopSpinnerSuccess(`Tunnel configuration updated: ${configName}.`);
|
|
3340
|
+
return;
|
|
3197
3341
|
}
|
|
3342
|
+
this.queuePendingAction("update", configId, configName, result.tunnelid);
|
|
3343
|
+
}
|
|
3344
|
+
clearPendingAction(configId, tunnelId) {
|
|
3345
|
+
const entry = this.pendingActions.find(configId, tunnelId);
|
|
3346
|
+
if (!entry) {
|
|
3347
|
+
return;
|
|
3348
|
+
}
|
|
3349
|
+
this.pendingActions.delete(entry.configId);
|
|
3350
|
+
}
|
|
3351
|
+
queuePendingAction(action, configId, configName, tunnelId) {
|
|
3352
|
+
this.cleanupExpiredPendingActions();
|
|
3353
|
+
this.pendingActions.queue({ action, configId, configName, queuedAt: Date.now(), tunnelId });
|
|
3354
|
+
}
|
|
3355
|
+
printActionComplete(action, configName, urls) {
|
|
3356
|
+
const message = action === "restart" ? `Restarted tunnel with config name: ${configName}` : `Tunnel configuration updated and restarted: ${configName}`;
|
|
3357
|
+
printer_default.stopSpinnerSuccess(message);
|
|
3358
|
+
printer_default.info(import_picocolors3.default.cyanBright("Remote URLs:"));
|
|
3359
|
+
urls.forEach((url) => printer_default.print(" " + import_picocolors3.default.magentaBright(url)));
|
|
3198
3360
|
}
|
|
3199
3361
|
monitorList(result) {
|
|
3200
3362
|
this.cleanupExpiredPendingStarts();
|
|
3201
|
-
|
|
3363
|
+
this.cleanupExpiredPendingActions();
|
|
3364
|
+
if (!Array.isArray(result)) {
|
|
3202
3365
|
return;
|
|
3203
3366
|
}
|
|
3204
3367
|
for (const tunnel of result) {
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3368
|
+
this.tunnelDetailsCache.set(tunnel.tunnelid, {
|
|
3369
|
+
configId: this.getConfigIdFromTunnel(tunnel),
|
|
3370
|
+
configName: this.getConfigNameFromTunnel(tunnel)
|
|
3371
|
+
});
|
|
3372
|
+
}
|
|
3373
|
+
this.resolvePendingFromList(
|
|
3374
|
+
result,
|
|
3375
|
+
this.pendingStarts,
|
|
3376
|
+
(entry, urls) => this.completePendingStart(entry, urls),
|
|
3377
|
+
(entry, reason) => printer_default.stopSpinnerFail(`Tunnel start did not complete for config name: ${entry.configName}. ${reason}`)
|
|
3378
|
+
);
|
|
3379
|
+
this.resolvePendingFromList(
|
|
3380
|
+
result,
|
|
3381
|
+
this.pendingActions,
|
|
3382
|
+
(entry, urls) => {
|
|
3383
|
+
this.pendingActions.delete(entry.configId);
|
|
3384
|
+
this.printActionComplete(entry.action, entry.configName, urls);
|
|
3385
|
+
},
|
|
3386
|
+
(entry, reason) => {
|
|
3387
|
+
const verb = entry.action === "restart" ? "Restart" : "Configuration update";
|
|
3388
|
+
printer_default.stopSpinnerFail(`${verb} did not complete for config name: ${entry.configName}. ${reason}`);
|
|
3208
3389
|
}
|
|
3209
|
-
|
|
3390
|
+
);
|
|
3391
|
+
}
|
|
3392
|
+
resolvePendingFromList(tunnels, tracker, onComplete, onExited) {
|
|
3393
|
+
if (!tracker.hasLatest) {
|
|
3394
|
+
return;
|
|
3395
|
+
}
|
|
3396
|
+
for (const tunnel of tunnels) {
|
|
3397
|
+
const entry = tracker.find(this.getConfigIdFromTunnel(tunnel), tunnel.tunnelid);
|
|
3398
|
+
if (!entry || !tracker.isLatest(entry.configId)) {
|
|
3210
3399
|
continue;
|
|
3211
3400
|
}
|
|
3212
|
-
|
|
3213
|
-
|
|
3401
|
+
entry.tunnelId = tunnel.tunnelid;
|
|
3402
|
+
tracker.upsert(entry);
|
|
3214
3403
|
if (tunnel.remoteurls.length > 0) {
|
|
3215
|
-
|
|
3404
|
+
onComplete(entry, tunnel.remoteurls);
|
|
3216
3405
|
continue;
|
|
3217
3406
|
}
|
|
3218
3407
|
if (tunnel.status.state === "exited" /* Exited */) {
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
this.latestPendingConfigId = void 0;
|
|
3222
|
-
printer_default.stopSpinnerFail(`Tunnel start did not complete for config name: ${pending.configName}. ${reason}`);
|
|
3408
|
+
tracker.delete(entry.configId);
|
|
3409
|
+
onExited(entry, tunnel.status.errormsg || "Tunnel exited before a public URL was assigned");
|
|
3223
3410
|
}
|
|
3224
3411
|
}
|
|
3225
3412
|
}
|
|
3226
3413
|
completePendingStart(entry, urls) {
|
|
3227
|
-
if (this.
|
|
3414
|
+
if (this.pendingStarts.latest && !this.pendingStarts.isLatest(entry.configId)) {
|
|
3228
3415
|
this.pendingStarts.delete(entry.configId);
|
|
3229
3416
|
return;
|
|
3230
3417
|
}
|
|
3231
3418
|
this.pendingStarts.delete(entry.configId);
|
|
3232
|
-
this.latestPendingConfigId = void 0;
|
|
3233
3419
|
printer_default.stopSpinnerSuccess(`Tunnel started with config name: ${entry.configName}.`);
|
|
3234
3420
|
printer_default.info(import_picocolors3.default.cyanBright("Remote URLs:"));
|
|
3235
3421
|
(urls ?? []).forEach(
|
|
@@ -3237,28 +3423,27 @@ var init_websocket_printer = __esm({
|
|
|
3237
3423
|
);
|
|
3238
3424
|
}
|
|
3239
3425
|
cleanupExpiredPendingStarts() {
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
if (
|
|
3243
|
-
|
|
3426
|
+
this.pendingStarts.cleanupExpired((entry, wasLatest) => {
|
|
3427
|
+
const message = `Timed out while waiting for tunnel URL for config name: ${entry.configName}`;
|
|
3428
|
+
if (wasLatest) {
|
|
3429
|
+
printer_default.stopSpinnerFail(message);
|
|
3430
|
+
} else {
|
|
3431
|
+
printer_default.warn(message);
|
|
3244
3432
|
}
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
logger.warn("Pending websocket start entry expired", { configId, tunnelId: entry.tunnelId });
|
|
3248
|
-
}
|
|
3433
|
+
logger.warn("Pending websocket start entry expired", { configId: entry.configId, tunnelId: entry.tunnelId });
|
|
3434
|
+
});
|
|
3249
3435
|
}
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
return entry;
|
|
3436
|
+
cleanupExpiredPendingActions() {
|
|
3437
|
+
this.pendingActions.cleanupExpired((entry, wasLatest) => {
|
|
3438
|
+
const verb = entry.action === "restart" ? "restart" : "configuration update";
|
|
3439
|
+
const message = `Timed out while waiting for tunnel URL after ${verb} for config name: ${entry.configName}`;
|
|
3440
|
+
if (wasLatest) {
|
|
3441
|
+
printer_default.stopSpinnerFail(message);
|
|
3442
|
+
} else {
|
|
3443
|
+
printer_default.warn(message);
|
|
3259
3444
|
}
|
|
3260
|
-
|
|
3261
|
-
|
|
3445
|
+
logger.warn("Pending websocket action entry expired", { configId: entry.configId, tunnelId: entry.tunnelId, action: entry.action });
|
|
3446
|
+
});
|
|
3262
3447
|
}
|
|
3263
3448
|
resolveTunnelDetails(tunnelId, result) {
|
|
3264
3449
|
if (result && !isErrorResponse(result)) {
|
|
@@ -3267,7 +3452,7 @@ var init_websocket_printer = __esm({
|
|
|
3267
3452
|
configName: this.getConfigNameFromTunnel(result)
|
|
3268
3453
|
};
|
|
3269
3454
|
}
|
|
3270
|
-
return {
|
|
3455
|
+
return this.tunnelDetailsCache.get(tunnelId) ?? {
|
|
3271
3456
|
configId: tunnelId,
|
|
3272
3457
|
configName: tunnelId
|
|
3273
3458
|
};
|
|
@@ -3346,7 +3531,6 @@ var init_websocket_handlers = __esm({
|
|
|
3346
3531
|
WebSocketCommandHandler = class {
|
|
3347
3532
|
constructor(handler) {
|
|
3348
3533
|
this.tunnelHandler = handler ?? new TunnelOperations();
|
|
3349
|
-
remoteManagementWebSocketPrinter.setTunnelHandler(this.tunnelHandler);
|
|
3350
3534
|
}
|
|
3351
3535
|
safeParse(text) {
|
|
3352
3536
|
if (!text) return void 0;
|
|
@@ -3413,8 +3597,10 @@ var init_websocket_handlers = __esm({
|
|
|
3413
3597
|
}
|
|
3414
3598
|
}
|
|
3415
3599
|
async handleStopReq(req, raw) {
|
|
3600
|
+
let tunnelID;
|
|
3416
3601
|
try {
|
|
3417
3602
|
const dc = StopSchema.parse(raw);
|
|
3603
|
+
tunnelID = dc.tunnelID;
|
|
3418
3604
|
remoteManagementWebSocketPrinter.printStopRequested(dc.tunnelID);
|
|
3419
3605
|
const result = await this.tunnelHandler.handleStop(dc.tunnelID);
|
|
3420
3606
|
remoteManagementWebSocketPrinter.handleStopResult(dc.tunnelID, result);
|
|
@@ -3424,6 +3610,9 @@ var init_websocket_handlers = __esm({
|
|
|
3424
3610
|
printer_default.warn("Validation failed for stop request");
|
|
3425
3611
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
3426
3612
|
}
|
|
3613
|
+
if (tunnelID) {
|
|
3614
|
+
remoteManagementWebSocketPrinter.handleStopResult(tunnelID, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
3615
|
+
}
|
|
3427
3616
|
printer_default.warn(`Error in handleStopReq error: ${String(e)}`);
|
|
3428
3617
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
3429
3618
|
}
|
|
@@ -3443,8 +3632,10 @@ var init_websocket_handlers = __esm({
|
|
|
3443
3632
|
}
|
|
3444
3633
|
}
|
|
3445
3634
|
async handleRestartReq(req, raw) {
|
|
3635
|
+
let tunnelID;
|
|
3446
3636
|
try {
|
|
3447
3637
|
const dc = RestartSchema.parse(raw);
|
|
3638
|
+
tunnelID = dc.tunnelID;
|
|
3448
3639
|
remoteManagementWebSocketPrinter.printRestartRequested(dc.tunnelID);
|
|
3449
3640
|
const result = await this.tunnelHandler.handleRestart(dc.tunnelID, true);
|
|
3450
3641
|
remoteManagementWebSocketPrinter.handleRestartResult(dc.tunnelID, result);
|
|
@@ -3454,34 +3645,51 @@ var init_websocket_handlers = __esm({
|
|
|
3454
3645
|
printer_default.warn("Validation failed for restart request");
|
|
3455
3646
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
3456
3647
|
}
|
|
3648
|
+
if (tunnelID) {
|
|
3649
|
+
remoteManagementWebSocketPrinter.handleRestartResult(tunnelID, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
3650
|
+
}
|
|
3457
3651
|
printer_default.warn(`Error in handleRestartReq error: ${String(e)}`);
|
|
3458
3652
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
3459
3653
|
}
|
|
3460
3654
|
}
|
|
3461
3655
|
async handleUpdateConfigReq(req, raw) {
|
|
3656
|
+
let parsedConfig;
|
|
3462
3657
|
try {
|
|
3463
3658
|
const dc = UpdateConfigSchema.parse(raw);
|
|
3659
|
+
parsedConfig = dc.tunnelConfig;
|
|
3660
|
+
remoteManagementWebSocketPrinter.printUpdateConfigRequested(dc.tunnelConfig);
|
|
3464
3661
|
const result = await this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig, true);
|
|
3662
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(dc.tunnelConfig, result);
|
|
3465
3663
|
return this.wrapResponse(result, req);
|
|
3466
3664
|
} catch (e) {
|
|
3467
3665
|
if (e instanceof import_zod2.default.ZodError) {
|
|
3468
3666
|
printer_default.warn("Validation failed for updateconfig request");
|
|
3469
3667
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
3470
3668
|
}
|
|
3669
|
+
if (parsedConfig) {
|
|
3670
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(parsedConfig, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
3671
|
+
}
|
|
3471
3672
|
printer_default.warn(`Error in handleUpdateConfigReq error: ${String(e)}`);
|
|
3472
3673
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
3473
3674
|
}
|
|
3474
3675
|
}
|
|
3475
3676
|
async handleUpdateConfigV2Req(req, raw) {
|
|
3677
|
+
let parsedConfig;
|
|
3476
3678
|
try {
|
|
3477
3679
|
const dc = UpdateConfigV2Schema.parse(raw);
|
|
3680
|
+
parsedConfig = dc.tunnelConfig;
|
|
3681
|
+
remoteManagementWebSocketPrinter.printUpdateConfigRequested(dc.tunnelConfig);
|
|
3478
3682
|
const result = await this.tunnelHandler.handleUpdateConfigV2(dc.tunnelConfig, true);
|
|
3683
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(dc.tunnelConfig, result);
|
|
3479
3684
|
return this.wrapResponse(result, req);
|
|
3480
3685
|
} catch (e) {
|
|
3481
3686
|
if (e instanceof import_zod2.default.ZodError) {
|
|
3482
3687
|
printer_default.warn("Validation failed for update-config-v2 request");
|
|
3483
3688
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
3484
3689
|
}
|
|
3690
|
+
if (parsedConfig) {
|
|
3691
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(parsedConfig, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
3692
|
+
}
|
|
3485
3693
|
printer_default.warn(`Error in handleUpdateConfigV2Req error: ${String(e)}`);
|
|
3486
3694
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
3487
3695
|
}
|
|
@@ -3916,10 +4124,20 @@ function parseExtendedOptions(options, config, localServerTls) {
|
|
|
3916
4124
|
case "fullrequesturl":
|
|
3917
4125
|
config.originalRequestUrl = true;
|
|
3918
4126
|
break;
|
|
4127
|
+
case "haproxy":
|
|
4128
|
+
case "haproxy:v1":
|
|
4129
|
+
config.haProxy = import_pinggy4.HaProxyVersion.V1;
|
|
4130
|
+
break;
|
|
4131
|
+
case "haproxy:v2":
|
|
4132
|
+
config.haProxy = import_pinggy4.HaProxyVersion.V2;
|
|
4133
|
+
break;
|
|
3919
4134
|
default: {
|
|
3920
4135
|
if (value && (value.startsWith("localServerTls") || value.startsWith("localservertls"))) {
|
|
3921
4136
|
const parts = value.split(/:(.+)/);
|
|
3922
4137
|
localServerTls = parts[1] ? parts[1] : "";
|
|
4138
|
+
} else if (value && value.startsWith("haproxy:")) {
|
|
4139
|
+
printer_default.warn(`Invalid HAProxy version in "${opt}". Use x:haproxy, x:haproxy:v1 or x:haproxy:v2`);
|
|
4140
|
+
logger.warn(`Warning: Invalid HAProxy version in "${opt}". Use x:haproxy, x:haproxy:v1 or x:haproxy:v2`);
|
|
3923
4141
|
} else {
|
|
3924
4142
|
printer_default.warn(`Unknown extended option "${value}"`);
|
|
3925
4143
|
logger.warn(`Warning: Unknown extended option "${value}"`);
|
|
@@ -4021,11 +4239,12 @@ function isValidIpV6Cidr(input) {
|
|
|
4021
4239
|
}
|
|
4022
4240
|
return false;
|
|
4023
4241
|
}
|
|
4024
|
-
var import_net;
|
|
4242
|
+
var import_pinggy4, import_net;
|
|
4025
4243
|
var init_extendedOptions = __esm({
|
|
4026
4244
|
"src/cli/extendedOptions.ts"() {
|
|
4027
4245
|
"use strict";
|
|
4028
4246
|
init_cjs_shims();
|
|
4247
|
+
import_pinggy4 = require("@pinggy/pinggy");
|
|
4029
4248
|
import_net = require("net");
|
|
4030
4249
|
init_logger();
|
|
4031
4250
|
init_printer();
|
|
@@ -4062,7 +4281,7 @@ function parseUserAndDomain(str) {
|
|
|
4062
4281
|
const [user, domain] = str.split("@", 2);
|
|
4063
4282
|
if (isValidServerAddress(domain)) {
|
|
4064
4283
|
let processKeyword2 = function(keyword) {
|
|
4065
|
-
if ([
|
|
4284
|
+
if ([import_pinggy5.TunnelType.Http, import_pinggy5.TunnelType.Tcp, import_pinggy5.TunnelType.Tls, import_pinggy5.TunnelType.Udp, import_pinggy5.TunnelType.TlsTcp].includes(keyword)) {
|
|
4066
4285
|
type = keyword;
|
|
4067
4286
|
} else if (keyword === "force") {
|
|
4068
4287
|
forceFlag = true;
|
|
@@ -4150,7 +4369,7 @@ function parseUsers(positionalArgs, explicitToken) {
|
|
|
4150
4369
|
}
|
|
4151
4370
|
function parseType(finalConfig, values, inferredType) {
|
|
4152
4371
|
const t = inferredType || values.type;
|
|
4153
|
-
if (t ===
|
|
4372
|
+
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) {
|
|
4154
4373
|
return t;
|
|
4155
4374
|
}
|
|
4156
4375
|
}
|
|
@@ -4248,7 +4467,7 @@ function parseAdditionalForwarding(forwarding) {
|
|
|
4248
4467
|
}
|
|
4249
4468
|
const firstPart = parsed[0];
|
|
4250
4469
|
const [hostPart] = firstPart.split("@");
|
|
4251
|
-
let protocol =
|
|
4470
|
+
let protocol = import_pinggy5.TunnelType.Http;
|
|
4252
4471
|
let remoteDomainRaw;
|
|
4253
4472
|
let remotePort = 0;
|
|
4254
4473
|
if (hostPart.includes("//")) {
|
|
@@ -4281,7 +4500,7 @@ function parseAdditionalForwarding(forwarding) {
|
|
|
4281
4500
|
if (!isValidServerAddress(remoteDomainRaw)) {
|
|
4282
4501
|
return new Error("invalid remote domain");
|
|
4283
4502
|
}
|
|
4284
|
-
protocol =
|
|
4503
|
+
protocol = import_pinggy5.TunnelType.Http;
|
|
4285
4504
|
remotePort = 0;
|
|
4286
4505
|
}
|
|
4287
4506
|
const localDomain = removeIPv6Brackets(parsed[2] || "localhost");
|
|
@@ -4311,7 +4530,7 @@ function parseReverseTunnelAddr(finalConfig, values, primaryType) {
|
|
|
4311
4530
|
if (parsed instanceof Error) return parsed;
|
|
4312
4531
|
forwardingData.push({
|
|
4313
4532
|
address: `${parsed.localDomain}:${parsed.localPort}`,
|
|
4314
|
-
type: primaryType ||
|
|
4533
|
+
type: primaryType || import_pinggy5.TunnelType.Http
|
|
4315
4534
|
});
|
|
4316
4535
|
} else if (slicedForwarding.length === 4) {
|
|
4317
4536
|
const parsed = parseAdditionalForwarding(forwarding);
|
|
@@ -4377,7 +4596,7 @@ function normalizeForwardingToArray(finalConfig, type) {
|
|
|
4377
4596
|
if (typeof fwd !== "string") {
|
|
4378
4597
|
return;
|
|
4379
4598
|
}
|
|
4380
|
-
finalConfig.forwarding = fwd.trim() === "" ? [] : [{ type: type ||
|
|
4599
|
+
finalConfig.forwarding = fwd.trim() === "" ? [] : [{ type: type || import_pinggy5.TunnelType.Http, address: fwd.trim() }];
|
|
4381
4600
|
}
|
|
4382
4601
|
function parseArgs2(finalConfig, remainingPositionals) {
|
|
4383
4602
|
let localserverTls = "";
|
|
@@ -4439,6 +4658,20 @@ function hasRemoteManagement(values) {
|
|
|
4439
4658
|
const token = values["remote-management"];
|
|
4440
4659
|
return typeof token === "string" && token.trim().length > 0;
|
|
4441
4660
|
}
|
|
4661
|
+
function validateHaProxy(finalConfig) {
|
|
4662
|
+
if (!finalConfig.haProxy) {
|
|
4663
|
+
return null;
|
|
4664
|
+
}
|
|
4665
|
+
const forwarding = finalConfig.forwarding;
|
|
4666
|
+
const entries = Array.isArray(forwarding) ? forwarding : [];
|
|
4667
|
+
const primaryType = entries.length > 0 ? entries[0].type : void 0;
|
|
4668
|
+
if (primaryType === import_pinggy5.TunnelType.Tcp) {
|
|
4669
|
+
return null;
|
|
4670
|
+
}
|
|
4671
|
+
return new Error(
|
|
4672
|
+
`HAProxy PROXY protocol (x:haproxy) can only be used with TCP tunnels, but this tunnel is "${primaryType ?? import_pinggy5.TunnelType.Http}". Make it a TCP tunnel (--type tcp, or the tcp@ server prefix) or drop x:haproxy.`
|
|
4673
|
+
);
|
|
4674
|
+
}
|
|
4442
4675
|
function buildFinalConfig(values, positionals, baseConfig) {
|
|
4443
4676
|
let token;
|
|
4444
4677
|
let server;
|
|
@@ -4501,10 +4734,14 @@ function buildFinalConfig(values, positionals, baseConfig) {
|
|
|
4501
4734
|
}
|
|
4502
4735
|
normalizeForwardingToArray(finalConfig, type);
|
|
4503
4736
|
parseArgs2(finalConfig, remainingPositionals);
|
|
4737
|
+
const haProxyErr = validateHaProxy(finalConfig);
|
|
4738
|
+
if (haProxyErr instanceof Error) {
|
|
4739
|
+
throw haProxyErr;
|
|
4740
|
+
}
|
|
4504
4741
|
storeJson(finalConfig, saveconf);
|
|
4505
4742
|
return finalConfig;
|
|
4506
4743
|
}
|
|
4507
|
-
var
|
|
4744
|
+
var import_pinggy5, import_fs6, import_path6, import_net2, domainRegex, KEYWORDS, VALID_PROTOCOLS;
|
|
4508
4745
|
var init_buildConfig = __esm({
|
|
4509
4746
|
"src/cli/buildConfig.ts"() {
|
|
4510
4747
|
"use strict";
|
|
@@ -4513,21 +4750,21 @@ var init_buildConfig = __esm({
|
|
|
4513
4750
|
init_extendedOptions();
|
|
4514
4751
|
init_logger();
|
|
4515
4752
|
init_util();
|
|
4516
|
-
|
|
4753
|
+
import_pinggy5 = require("@pinggy/pinggy");
|
|
4517
4754
|
import_fs6 = __toESM(require("fs"), 1);
|
|
4518
4755
|
import_path6 = __toESM(require("path"), 1);
|
|
4519
4756
|
import_net2 = require("net");
|
|
4520
4757
|
domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
|
|
4521
4758
|
KEYWORDS = /* @__PURE__ */ new Set([
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4759
|
+
import_pinggy5.TunnelType.Http,
|
|
4760
|
+
import_pinggy5.TunnelType.Tcp,
|
|
4761
|
+
import_pinggy5.TunnelType.Tls,
|
|
4762
|
+
import_pinggy5.TunnelType.Udp,
|
|
4763
|
+
import_pinggy5.TunnelType.TlsTcp,
|
|
4527
4764
|
"force",
|
|
4528
4765
|
"qr"
|
|
4529
4766
|
]);
|
|
4530
|
-
VALID_PROTOCOLS = [
|
|
4767
|
+
VALID_PROTOCOLS = [import_pinggy5.TunnelType.Http, import_pinggy5.TunnelType.Tcp, import_pinggy5.TunnelType.Udp, import_pinggy5.TunnelType.Tls, import_pinggy5.TunnelType.TlsTcp];
|
|
4531
4768
|
}
|
|
4532
4769
|
});
|
|
4533
4770
|
|
|
@@ -5111,7 +5348,7 @@ var init_webDebuggerConnection = __esm({
|
|
|
5111
5348
|
});
|
|
5112
5349
|
|
|
5113
5350
|
// src/tui/ink/asciArt.ts
|
|
5114
|
-
var asciiArtPinggyLogo;
|
|
5351
|
+
var asciiArtPinggyLogo, asciiArtPinggyLogoCompact, asciiArtPinggyLogoMedium;
|
|
5115
5352
|
var init_asciArt = __esm({
|
|
5116
5353
|
"src/tui/ink/asciArt.ts"() {
|
|
5117
5354
|
"use strict";
|
|
@@ -5123,6 +5360,13 @@ var init_asciArt = __esm({
|
|
|
5123
5360
|
\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2554\u255D
|
|
5124
5361
|
\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551
|
|
5125
5362
|
\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D `;
|
|
5363
|
+
asciiArtPinggyLogoCompact = "P I N G G Y";
|
|
5364
|
+
asciiArtPinggyLogoMedium = [
|
|
5365
|
+
" ___ ___ _ _ ___ _____ __",
|
|
5366
|
+
"| _ \\_ _| \\| |/ __|/ __\\ \\ / /",
|
|
5367
|
+
"| _/| || .` | (_ | (_ |\\ V /",
|
|
5368
|
+
"|_| |___|_|\\_|\\___|\\___| |_|"
|
|
5369
|
+
].join("\n");
|
|
5126
5370
|
}
|
|
5127
5371
|
});
|
|
5128
5372
|
|
|
@@ -5135,6 +5379,54 @@ function colorizeGradient(text) {
|
|
|
5135
5379
|
return `{${color}-fg}${line}{/${color}-fg}`;
|
|
5136
5380
|
}).join("\n");
|
|
5137
5381
|
}
|
|
5382
|
+
function colorizeGradientHorizontal(text) {
|
|
5383
|
+
const colors = ["red", "yellow", "green", "cyan", "blue", "magenta"];
|
|
5384
|
+
let colorIndex = 0;
|
|
5385
|
+
return [...text].map((char) => {
|
|
5386
|
+
if (char === " ") return char;
|
|
5387
|
+
const color = colors[colorIndex % colors.length];
|
|
5388
|
+
colorIndex++;
|
|
5389
|
+
return `{${color}-fg}${char}{/${color}-fg}`;
|
|
5390
|
+
}).join("");
|
|
5391
|
+
}
|
|
5392
|
+
function selectLogo(width, height) {
|
|
5393
|
+
const widthTier = width >= FULL_LOGO_THRESHOLD ? 2 : width >= MEDIUM_LOGO_THRESHOLD ? 1 : 0;
|
|
5394
|
+
const heightTier = height >= FULL_LOGO_HEIGHT_THRESHOLD ? 2 : height >= MEDIUM_LOGO_HEIGHT_THRESHOLD ? 1 : 0;
|
|
5395
|
+
const tier = Math.min(widthTier, heightTier);
|
|
5396
|
+
if (tier === 2) {
|
|
5397
|
+
return { content: colorizeGradient(asciiArtPinggyLogo), boxHeight: 7 };
|
|
5398
|
+
}
|
|
5399
|
+
if (tier === 1) {
|
|
5400
|
+
return { content: colorizeGradient(asciiArtPinggyLogoMedium), boxHeight: 5 };
|
|
5401
|
+
}
|
|
5402
|
+
return {
|
|
5403
|
+
content: `{bold}${colorizeGradientHorizontal(asciiArtPinggyLogoCompact)}{/bold}`,
|
|
5404
|
+
boxHeight: 1
|
|
5405
|
+
};
|
|
5406
|
+
}
|
|
5407
|
+
function wrapText(text, width) {
|
|
5408
|
+
const lines = [];
|
|
5409
|
+
let current = "";
|
|
5410
|
+
for (const word of text.split(/\s+/).filter(Boolean)) {
|
|
5411
|
+
const candidate = current ? `${current} ${word}` : word;
|
|
5412
|
+
if (candidate.length <= width) {
|
|
5413
|
+
current = candidate;
|
|
5414
|
+
continue;
|
|
5415
|
+
}
|
|
5416
|
+
if (current) {
|
|
5417
|
+
lines.push(current);
|
|
5418
|
+
current = "";
|
|
5419
|
+
}
|
|
5420
|
+
let rest = word;
|
|
5421
|
+
while (rest.length > width) {
|
|
5422
|
+
lines.push(rest.slice(0, width));
|
|
5423
|
+
rest = rest.slice(width);
|
|
5424
|
+
}
|
|
5425
|
+
current = rest;
|
|
5426
|
+
}
|
|
5427
|
+
if (current) lines.push(current);
|
|
5428
|
+
return lines.length ? lines : [""];
|
|
5429
|
+
}
|
|
5138
5430
|
function createWarningUI(screen) {
|
|
5139
5431
|
return import_blessed.default.box({
|
|
5140
5432
|
parent: screen,
|
|
@@ -5161,21 +5453,26 @@ function createFullUI(screen, urls, greet, tunnelConfig) {
|
|
|
5161
5453
|
height: "100%",
|
|
5162
5454
|
padding: 1
|
|
5163
5455
|
});
|
|
5456
|
+
const { content: logoContent, boxHeight: logoHeight } = selectLogo(
|
|
5457
|
+
screen.width,
|
|
5458
|
+
screen.height
|
|
5459
|
+
);
|
|
5164
5460
|
const logoBox = import_blessed.default.box({
|
|
5165
5461
|
parent: mainContainer,
|
|
5166
5462
|
top: 0,
|
|
5167
5463
|
left: 0,
|
|
5168
5464
|
width: "100%",
|
|
5169
|
-
height:
|
|
5170
|
-
content:
|
|
5465
|
+
height: logoHeight,
|
|
5466
|
+
content: logoContent,
|
|
5171
5467
|
tags: true
|
|
5172
5468
|
});
|
|
5469
|
+
const contentTop = logoHeight + 1;
|
|
5173
5470
|
const contentBox = import_blessed.default.box({
|
|
5174
5471
|
parent: mainContainer,
|
|
5175
|
-
top:
|
|
5472
|
+
top: contentTop,
|
|
5176
5473
|
left: 0,
|
|
5177
5474
|
width: "100%-2",
|
|
5178
|
-
height:
|
|
5475
|
+
height: `100%-${contentTop + 2}`,
|
|
5179
5476
|
padding: 0,
|
|
5180
5477
|
border: {
|
|
5181
5478
|
type: "line"
|
|
@@ -5188,20 +5485,24 @@ function createFullUI(screen, urls, greet, tunnelConfig) {
|
|
|
5188
5485
|
});
|
|
5189
5486
|
let greetHeight = 0;
|
|
5190
5487
|
if (greet) {
|
|
5191
|
-
const
|
|
5488
|
+
const contentInnerWidth = screen.width - 6;
|
|
5489
|
+
const greetWidth = Math.max(20, Math.floor(contentInnerWidth * 0.6));
|
|
5490
|
+
const greetLines = wrapText(greet, greetWidth);
|
|
5491
|
+
const greetBoxHeight = greetLines.length + 1;
|
|
5492
|
+
import_blessed.default.box({
|
|
5192
5493
|
parent: contentBox,
|
|
5193
5494
|
top: 0,
|
|
5194
5495
|
left: "center",
|
|
5195
|
-
width:
|
|
5196
|
-
height:
|
|
5197
|
-
content: `{bold}${
|
|
5496
|
+
width: greetWidth,
|
|
5497
|
+
height: greetBoxHeight,
|
|
5498
|
+
content: greetLines.map((line) => `{bold}${line}{/bold}`).join("\n"),
|
|
5198
5499
|
tags: true,
|
|
5199
5500
|
align: "center",
|
|
5200
5501
|
style: {
|
|
5201
5502
|
fg: "green"
|
|
5202
5503
|
}
|
|
5203
5504
|
});
|
|
5204
|
-
greetHeight =
|
|
5505
|
+
greetHeight = greetBoxHeight;
|
|
5205
5506
|
}
|
|
5206
5507
|
const upperSectionTop = greetHeight > 0 ? greetHeight : 0;
|
|
5207
5508
|
const upperSection = import_blessed.default.box({
|
|
@@ -5295,21 +5596,22 @@ function createSimpleUI(screen, urls, greet) {
|
|
|
5295
5596
|
});
|
|
5296
5597
|
let currentTop = 0;
|
|
5297
5598
|
if (greet) {
|
|
5599
|
+
const greetWidth = Math.max(20, Math.floor((screen.width - 2) * 0.9));
|
|
5600
|
+
const greetLines = wrapText(greet, greetWidth);
|
|
5298
5601
|
import_blessed.default.box({
|
|
5299
5602
|
parent: mainContainer,
|
|
5300
5603
|
top: currentTop,
|
|
5301
5604
|
left: "center",
|
|
5302
|
-
width:
|
|
5303
|
-
height:
|
|
5304
|
-
content: `{bold}${
|
|
5605
|
+
width: greetWidth,
|
|
5606
|
+
height: greetLines.length,
|
|
5607
|
+
content: greetLines.map((line) => `{bold}${line}{/bold}`).join("\n"),
|
|
5305
5608
|
tags: true,
|
|
5306
5609
|
align: "center",
|
|
5307
5610
|
style: {
|
|
5308
5611
|
fg: "green"
|
|
5309
5612
|
}
|
|
5310
5613
|
});
|
|
5311
|
-
|
|
5312
|
-
currentTop += Math.max(lines, 1) + 1;
|
|
5614
|
+
currentTop += greetLines.length + 1;
|
|
5313
5615
|
}
|
|
5314
5616
|
const urlsBox = import_blessed.default.box({
|
|
5315
5617
|
parent: mainContainer,
|
|
@@ -5348,7 +5650,7 @@ function createSimpleUI(screen, urls, greet) {
|
|
|
5348
5650
|
footerBox
|
|
5349
5651
|
};
|
|
5350
5652
|
}
|
|
5351
|
-
var import_blessed, MIN_WIDTH_WARNING, SIMPLE_LAYOUT_THRESHOLD;
|
|
5653
|
+
var import_blessed, MIN_WIDTH_WARNING, SIMPLE_LAYOUT_THRESHOLD, MEDIUM_LOGO_THRESHOLD, FULL_LOGO_THRESHOLD, MEDIUM_LOGO_HEIGHT_THRESHOLD, FULL_LOGO_HEIGHT_THRESHOLD;
|
|
5352
5654
|
var init_UIComponents = __esm({
|
|
5353
5655
|
"src/tui/blessed/components/UIComponents.ts"() {
|
|
5354
5656
|
"use strict";
|
|
@@ -5357,6 +5659,10 @@ var init_UIComponents = __esm({
|
|
|
5357
5659
|
init_asciArt();
|
|
5358
5660
|
MIN_WIDTH_WARNING = 60;
|
|
5359
5661
|
SIMPLE_LAYOUT_THRESHOLD = 80;
|
|
5662
|
+
MEDIUM_LOGO_THRESHOLD = 100;
|
|
5663
|
+
FULL_LOGO_THRESHOLD = 120;
|
|
5664
|
+
MEDIUM_LOGO_HEIGHT_THRESHOLD = 26;
|
|
5665
|
+
FULL_LOGO_HEIGHT_THRESHOLD = 34;
|
|
5360
5666
|
}
|
|
5361
5667
|
});
|
|
5362
5668
|
|
|
@@ -8322,7 +8628,7 @@ var init_ipcServer = __esm({
|
|
|
8322
8628
|
},
|
|
8323
8629
|
[Route.RestartTunnel]: async (req) => {
|
|
8324
8630
|
if (!req.tunnelid) throw new Error("Missing 'tunnelid' field");
|
|
8325
|
-
return await this.ops.handleRestart(req.tunnelid);
|
|
8631
|
+
return await this.ops.handleRestart(req.tunnelid, req.noWait);
|
|
8326
8632
|
},
|
|
8327
8633
|
[Route.UpdateConfig]: async (req) => {
|
|
8328
8634
|
if (!req.config) throw new Error("Missing 'config' field");
|
|
@@ -9817,9 +10123,9 @@ var init_tunnelClient = __esm({
|
|
|
9817
10123
|
this.assertClient();
|
|
9818
10124
|
return this.ipc.getTunnel(tunnelId);
|
|
9819
10125
|
}
|
|
9820
|
-
async handleRestart(tunnelId) {
|
|
10126
|
+
async handleRestart(tunnelId, noWait) {
|
|
9821
10127
|
this.assertClient();
|
|
9822
|
-
return this.ipc.restartTunnel(tunnelId);
|
|
10128
|
+
return this.ipc.restartTunnel(tunnelId, noWait);
|
|
9823
10129
|
}
|
|
9824
10130
|
async shutdown() {
|
|
9825
10131
|
this.assertClient();
|