pinggy 0.5.3 → 0.5.5
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 +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-BBPR2LJB.js → chunk-NBR2L6LW.js} +21 -9
- package/dist/{chunk-7G6SJEEA.js → chunk-P7WNBN7F.js} +1 -1
- package/dist/{chunk-DLNUDW6G.js → chunk-PQSLSIDA.js} +67 -13
- package/dist/{chunk-GK2PQ7KX.js → chunk-UX64XCC2.js} +214 -74
- 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-ZACX7V6J.js} +6 -6
- package/dist/index.cjs +389 -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-KG3Q4LXI.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,9 @@ 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()
|
|
2389
2446
|
});
|
|
2390
2447
|
TUNNEL_CONFIG_V1_KEYS = Object.keys(
|
|
2391
2448
|
TunnelConfigV1Schema.shape
|
|
@@ -2525,11 +2582,11 @@ var init_handler = __esm({
|
|
|
2525
2582
|
startPromise.catch((err) => {
|
|
2526
2583
|
logger.error("No-wait startTunnel failed", { tunnelid, err: String(err) });
|
|
2527
2584
|
});
|
|
2528
|
-
return this.buildPendingTunnelResponseV2(tunnelid, tunnelConfig, config, config.configId, config.name, config.
|
|
2585
|
+
return this.buildPendingTunnelResponseV2(tunnelid, tunnelConfig, config, config.configId, config.name, config.serve);
|
|
2529
2586
|
}
|
|
2530
2587
|
await startPromise;
|
|
2531
2588
|
const tunnelPconfig = await this.tunnelManager.getTunnelConfig("", tunnelid);
|
|
2532
|
-
return this.buildTunnelResponseV2(tunnelid, tunnelPconfig, config, config.configId, config.name, config.
|
|
2589
|
+
return this.buildTunnelResponseV2(tunnelid, tunnelPconfig, config, config.configId, config.name, config.serve);
|
|
2533
2590
|
} catch (err) {
|
|
2534
2591
|
if (err instanceof TunnelAlreadyRunningError) {
|
|
2535
2592
|
return this.error(ErrorCode.TunnelAlreadyRunningError, err, err.message);
|
|
@@ -2551,10 +2608,12 @@ var init_handler = __esm({
|
|
|
2551
2608
|
if (noWait) {
|
|
2552
2609
|
const existing = this.tunnelManager.getManagedTunnel(config.configid);
|
|
2553
2610
|
if (!existing.tunnelConfig) throw new Error("Invalid tunnel state before configuration update");
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2611
|
+
if (!existing.isStopped) {
|
|
2612
|
+
this.tunnelManager.updateConfig(updateOpts).catch((err) => {
|
|
2613
|
+
logger.error("No-wait updateConfig failed", { configid: config.configid, err: String(err) });
|
|
2614
|
+
});
|
|
2615
|
+
return this.buildPendingTunnelResponse(existing.tunnelid, existing.tunnelConfig, config.configid, existing.tunnelName, existing.serve);
|
|
2616
|
+
}
|
|
2558
2617
|
}
|
|
2559
2618
|
const tunnel = await this.tunnelManager.updateConfig(updateOpts);
|
|
2560
2619
|
if (!tunnel.instance || !tunnel.tunnelConfig)
|
|
@@ -2569,10 +2628,12 @@ var init_handler = __esm({
|
|
|
2569
2628
|
if (noWait) {
|
|
2570
2629
|
const existing = this.tunnelManager.getManagedTunnel(config.configId);
|
|
2571
2630
|
if (!existing.tunnelConfig) throw new Error("Invalid tunnel state before configuration update");
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2631
|
+
if (!existing.isStopped) {
|
|
2632
|
+
this.tunnelManager.updateConfig(config).catch((err) => {
|
|
2633
|
+
logger.error("No-wait updateConfigV2 failed", { configId: config.configId, err: String(err) });
|
|
2634
|
+
});
|
|
2635
|
+
return this.buildPendingTunnelResponseV2(existing.tunnelid, existing.tunnelConfig, config, config.configId, existing.tunnelName, existing.serve);
|
|
2636
|
+
}
|
|
2576
2637
|
}
|
|
2577
2638
|
const tunnel = await this.tunnelManager.updateConfig(config);
|
|
2578
2639
|
if (!tunnel.instance || !tunnel.tunnelConfig)
|
|
@@ -2795,8 +2856,8 @@ var init_ipcClient = __esm({
|
|
|
2795
2856
|
async stopTunnel(tunnelid) {
|
|
2796
2857
|
return this.call(Route.StopTunnel, { tunnelid });
|
|
2797
2858
|
}
|
|
2798
|
-
async restartTunnel(tunnelid) {
|
|
2799
|
-
return this.call(Route.RestartTunnel, { tunnelid });
|
|
2859
|
+
async restartTunnel(tunnelid, noWait) {
|
|
2860
|
+
return this.call(Route.RestartTunnel, { tunnelid, noWait });
|
|
2800
2861
|
}
|
|
2801
2862
|
// v1 operations (used by remote management via daemon)
|
|
2802
2863
|
async startTunnelV1(config, mode, noWait) {
|
|
@@ -2978,6 +3039,7 @@ function printHelpMessage() {
|
|
|
2978
3039
|
console.log(" x:noreverseproxy Disable built-in reverse-proxy header injection");
|
|
2979
3040
|
console.log(" x:localservertls:host Connect to local HTTPS server with SNI");
|
|
2980
3041
|
console.log(" x:passpreflight Pass CORS preflight requests unchanged");
|
|
3042
|
+
console.log(" x:haproxy[:v1|:v2] Enable HAProxy PROXY protocol (default v1)");
|
|
2981
3043
|
console.log(" a:Key:Val Add header");
|
|
2982
3044
|
console.log(" u:Key:Val Update header");
|
|
2983
3045
|
console.log(" r:Key Remove header");
|
|
@@ -3103,7 +3165,7 @@ var init_parseArgs = __esm({
|
|
|
3103
3165
|
});
|
|
3104
3166
|
|
|
3105
3167
|
// src/remote_management/websocket_printer.ts
|
|
3106
|
-
var import_picocolors3,
|
|
3168
|
+
var import_picocolors3, PENDING_TIMEOUT_MS, PendingTracker, RemoteManagementWebSocketPrinter, remoteManagementWebSocketPrinter;
|
|
3107
3169
|
var init_websocket_printer = __esm({
|
|
3108
3170
|
"src/remote_management/websocket_printer.ts"() {
|
|
3109
3171
|
"use strict";
|
|
@@ -3112,13 +3174,71 @@ var init_websocket_printer = __esm({
|
|
|
3112
3174
|
init_types();
|
|
3113
3175
|
init_printer();
|
|
3114
3176
|
import_picocolors3 = __toESM(require("picocolors"), 1);
|
|
3115
|
-
|
|
3116
|
-
|
|
3177
|
+
PENDING_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3178
|
+
PendingTracker = class {
|
|
3117
3179
|
constructor() {
|
|
3118
|
-
this.
|
|
3180
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
3181
|
+
}
|
|
3182
|
+
queue(entry) {
|
|
3183
|
+
this.latestConfigId = entry.configId;
|
|
3184
|
+
this.entries.set(entry.configId, entry);
|
|
3119
3185
|
}
|
|
3120
|
-
|
|
3121
|
-
|
|
3186
|
+
/** Store an entry without claiming the latest cursor. */
|
|
3187
|
+
upsert(entry) {
|
|
3188
|
+
this.entries.set(entry.configId, entry);
|
|
3189
|
+
}
|
|
3190
|
+
get(configId) {
|
|
3191
|
+
return this.entries.get(configId);
|
|
3192
|
+
}
|
|
3193
|
+
find(configId, tunnelId) {
|
|
3194
|
+
const byConfigId = this.entries.get(configId);
|
|
3195
|
+
if (byConfigId) {
|
|
3196
|
+
return byConfigId;
|
|
3197
|
+
}
|
|
3198
|
+
if (!tunnelId) {
|
|
3199
|
+
return void 0;
|
|
3200
|
+
}
|
|
3201
|
+
for (const entry of this.entries.values()) {
|
|
3202
|
+
if (entry.tunnelId === tunnelId) {
|
|
3203
|
+
return entry;
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
return void 0;
|
|
3207
|
+
}
|
|
3208
|
+
delete(configId) {
|
|
3209
|
+
this.entries.delete(configId);
|
|
3210
|
+
if (this.latestConfigId === configId) {
|
|
3211
|
+
this.latestConfigId = void 0;
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
get latest() {
|
|
3215
|
+
return this.latestConfigId;
|
|
3216
|
+
}
|
|
3217
|
+
get hasLatest() {
|
|
3218
|
+
return this.entries.size > 0 && this.latestConfigId !== void 0;
|
|
3219
|
+
}
|
|
3220
|
+
isLatest(configId) {
|
|
3221
|
+
return configId === this.latestConfigId;
|
|
3222
|
+
}
|
|
3223
|
+
cleanupExpired(onTimeout) {
|
|
3224
|
+
const now = Date.now();
|
|
3225
|
+
for (const [configId, entry] of this.entries) {
|
|
3226
|
+
if (now - entry.queuedAt <= PENDING_TIMEOUT_MS) {
|
|
3227
|
+
continue;
|
|
3228
|
+
}
|
|
3229
|
+
const wasLatest = this.isLatest(configId);
|
|
3230
|
+
this.delete(configId);
|
|
3231
|
+
onTimeout(entry, wasLatest);
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
};
|
|
3235
|
+
RemoteManagementWebSocketPrinter = class {
|
|
3236
|
+
constructor() {
|
|
3237
|
+
this.pendingStarts = new PendingTracker();
|
|
3238
|
+
this.pendingActions = new PendingTracker();
|
|
3239
|
+
// tunnelId -> details, fed by monitorList. Stop/restart requests only carry
|
|
3240
|
+
// a tunnelId; without this the spinner shows the raw id.
|
|
3241
|
+
this.tunnelDetailsCache = /* @__PURE__ */ new Map();
|
|
3122
3242
|
}
|
|
3123
3243
|
queueStart(config) {
|
|
3124
3244
|
this.cleanupExpiredPendingStarts();
|
|
@@ -3127,24 +3247,23 @@ var init_websocket_printer = __esm({
|
|
|
3127
3247
|
configName: this.getConfigNameFromRequest(config),
|
|
3128
3248
|
queuedAt: Date.now()
|
|
3129
3249
|
};
|
|
3130
|
-
this.
|
|
3131
|
-
this.pendingStarts.set(entry.configId, entry);
|
|
3250
|
+
this.pendingStarts.queue(entry);
|
|
3132
3251
|
printer_default.startSpinner("Starting tunnel with config name: " + entry.configName);
|
|
3133
3252
|
}
|
|
3134
3253
|
failQueuedStart(config, reason) {
|
|
3135
3254
|
const configId = this.getConfigIdFromRequest(config);
|
|
3136
3255
|
const pending = this.pendingStarts.get(configId);
|
|
3137
3256
|
const configName = pending?.configName || this.getConfigNameFromRequest(config);
|
|
3257
|
+
const wasLatest = this.pendingStarts.isLatest(configId);
|
|
3138
3258
|
this.pendingStarts.delete(configId);
|
|
3139
|
-
if (
|
|
3140
|
-
this.latestPendingConfigId = void 0;
|
|
3259
|
+
if (wasLatest) {
|
|
3141
3260
|
printer_default.stopSpinnerFail(`Failed to start tunnel with config name: ${configName}. ${reason}`);
|
|
3142
3261
|
}
|
|
3143
3262
|
}
|
|
3144
3263
|
handleStartResult(config, result) {
|
|
3145
3264
|
this.cleanupExpiredPendingStarts();
|
|
3146
3265
|
const requestedConfigId = this.getConfigIdFromRequest(config);
|
|
3147
|
-
if (this.
|
|
3266
|
+
if (this.pendingStarts.latest && !this.pendingStarts.isLatest(requestedConfigId)) {
|
|
3148
3267
|
this.pendingStarts.delete(requestedConfigId);
|
|
3149
3268
|
return;
|
|
3150
3269
|
}
|
|
@@ -3152,20 +3271,20 @@ var init_websocket_printer = __esm({
|
|
|
3152
3271
|
this.failQueuedStart(config, result.message);
|
|
3153
3272
|
return;
|
|
3154
3273
|
}
|
|
3155
|
-
const configId = this.getConfigIdFromTunnel(result);
|
|
3156
3274
|
const pending = this.pendingStarts.get(requestedConfigId) || {
|
|
3157
3275
|
configId: requestedConfigId,
|
|
3158
3276
|
configName: this.getConfigNameFromRequest(config),
|
|
3159
3277
|
queuedAt: Date.now()
|
|
3160
3278
|
};
|
|
3161
3279
|
pending.tunnelId = result.tunnelid;
|
|
3162
|
-
this.pendingStarts.
|
|
3280
|
+
this.pendingStarts.upsert(pending);
|
|
3163
3281
|
if (result.remoteurls.length > 0) {
|
|
3164
3282
|
this.completePendingStart(pending, result.remoteurls);
|
|
3165
3283
|
}
|
|
3166
3284
|
}
|
|
3167
3285
|
printStopRequested(tunnelId) {
|
|
3168
3286
|
const details = this.resolveTunnelDetails(tunnelId);
|
|
3287
|
+
this.clearPendingAction(details.configId, tunnelId);
|
|
3169
3288
|
printer_default.startSpinner("Stopping tunnel with config name: " + details.configName);
|
|
3170
3289
|
}
|
|
3171
3290
|
handleStopResult(tunnelId, result) {
|
|
@@ -3179,6 +3298,7 @@ var init_websocket_printer = __esm({
|
|
|
3179
3298
|
}
|
|
3180
3299
|
printRestartRequested(tunnelId) {
|
|
3181
3300
|
const details = this.resolveTunnelDetails(tunnelId);
|
|
3301
|
+
this.clearPendingAction(details.configId, tunnelId);
|
|
3182
3302
|
printer_default.startSpinner("Restarting tunnel with config name: " + details.configName);
|
|
3183
3303
|
}
|
|
3184
3304
|
handleRestartResult(tunnelId, result) {
|
|
@@ -3188,48 +3308,101 @@ var init_websocket_printer = __esm({
|
|
|
3188
3308
|
printer_default.stopSpinnerFail("Failed to restart tunnel with config name: " + details.configName);
|
|
3189
3309
|
return;
|
|
3190
3310
|
}
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3311
|
+
this.queuePendingAction("restart", details.configId, details.configName, result.tunnelid);
|
|
3312
|
+
}
|
|
3313
|
+
printUpdateConfigRequested(config) {
|
|
3314
|
+
const configName = this.getConfigNameFromRequest(config);
|
|
3315
|
+
this.clearPendingAction(this.getConfigIdFromRequest(config));
|
|
3316
|
+
printer_default.startSpinner("Updating tunnel configuration for: " + configName);
|
|
3317
|
+
}
|
|
3318
|
+
handleUpdateConfigResult(config, result) {
|
|
3319
|
+
const configId = this.getConfigIdFromRequest(config);
|
|
3320
|
+
const configName = this.getConfigNameFromRequest(config);
|
|
3321
|
+
if (isErrorResponse(result)) {
|
|
3322
|
+
printer_default.stopSpinnerFail(`Failed to update tunnel configuration for: ${configName}. ${result.message}`);
|
|
3323
|
+
return;
|
|
3324
|
+
}
|
|
3325
|
+
if (result.status.state === "exited" /* Exited */) {
|
|
3326
|
+
printer_default.stopSpinnerSuccess(`Tunnel configuration updated: ${configName}.`);
|
|
3327
|
+
return;
|
|
3197
3328
|
}
|
|
3329
|
+
this.queuePendingAction("update", configId, configName, result.tunnelid);
|
|
3330
|
+
}
|
|
3331
|
+
clearPendingAction(configId, tunnelId) {
|
|
3332
|
+
const entry = this.pendingActions.find(configId, tunnelId);
|
|
3333
|
+
if (!entry) {
|
|
3334
|
+
return;
|
|
3335
|
+
}
|
|
3336
|
+
this.pendingActions.delete(entry.configId);
|
|
3337
|
+
}
|
|
3338
|
+
queuePendingAction(action, configId, configName, tunnelId) {
|
|
3339
|
+
this.cleanupExpiredPendingActions();
|
|
3340
|
+
this.pendingActions.queue({ action, configId, configName, queuedAt: Date.now(), tunnelId });
|
|
3341
|
+
}
|
|
3342
|
+
printActionComplete(action, configName, urls) {
|
|
3343
|
+
const message = action === "restart" ? `Restarted tunnel with config name: ${configName}` : `Tunnel configuration updated and restarted: ${configName}`;
|
|
3344
|
+
printer_default.stopSpinnerSuccess(message);
|
|
3345
|
+
printer_default.info(import_picocolors3.default.cyanBright("Remote URLs:"));
|
|
3346
|
+
urls.forEach((url) => printer_default.print(" " + import_picocolors3.default.magentaBright(url)));
|
|
3198
3347
|
}
|
|
3199
3348
|
monitorList(result) {
|
|
3200
3349
|
this.cleanupExpiredPendingStarts();
|
|
3201
|
-
|
|
3350
|
+
this.cleanupExpiredPendingActions();
|
|
3351
|
+
if (!Array.isArray(result)) {
|
|
3202
3352
|
return;
|
|
3203
3353
|
}
|
|
3204
3354
|
for (const tunnel of result) {
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3355
|
+
this.tunnelDetailsCache.set(tunnel.tunnelid, {
|
|
3356
|
+
configId: this.getConfigIdFromTunnel(tunnel),
|
|
3357
|
+
configName: this.getConfigNameFromTunnel(tunnel)
|
|
3358
|
+
});
|
|
3359
|
+
}
|
|
3360
|
+
this.resolvePendingFromList(
|
|
3361
|
+
result,
|
|
3362
|
+
this.pendingStarts,
|
|
3363
|
+
(entry, urls) => this.completePendingStart(entry, urls),
|
|
3364
|
+
(entry, reason) => printer_default.stopSpinnerFail(`Tunnel start did not complete for config name: ${entry.configName}. ${reason}`)
|
|
3365
|
+
);
|
|
3366
|
+
this.resolvePendingFromList(
|
|
3367
|
+
result,
|
|
3368
|
+
this.pendingActions,
|
|
3369
|
+
(entry, urls) => {
|
|
3370
|
+
this.pendingActions.delete(entry.configId);
|
|
3371
|
+
this.printActionComplete(entry.action, entry.configName, urls);
|
|
3372
|
+
},
|
|
3373
|
+
(entry, reason) => {
|
|
3374
|
+
const verb = entry.action === "restart" ? "Restart" : "Configuration update";
|
|
3375
|
+
printer_default.stopSpinnerFail(`${verb} did not complete for config name: ${entry.configName}. ${reason}`);
|
|
3208
3376
|
}
|
|
3209
|
-
|
|
3377
|
+
);
|
|
3378
|
+
}
|
|
3379
|
+
resolvePendingFromList(tunnels, tracker, onComplete, onExited) {
|
|
3380
|
+
if (!tracker.hasLatest) {
|
|
3381
|
+
return;
|
|
3382
|
+
}
|
|
3383
|
+
for (const tunnel of tunnels) {
|
|
3384
|
+
const entry = tracker.find(this.getConfigIdFromTunnel(tunnel), tunnel.tunnelid);
|
|
3385
|
+
if (!entry || !tracker.isLatest(entry.configId)) {
|
|
3210
3386
|
continue;
|
|
3211
3387
|
}
|
|
3212
|
-
|
|
3213
|
-
|
|
3388
|
+
entry.tunnelId = tunnel.tunnelid;
|
|
3389
|
+
tracker.upsert(entry);
|
|
3214
3390
|
if (tunnel.remoteurls.length > 0) {
|
|
3215
|
-
|
|
3391
|
+
onComplete(entry, tunnel.remoteurls);
|
|
3216
3392
|
continue;
|
|
3217
3393
|
}
|
|
3218
3394
|
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}`);
|
|
3395
|
+
tracker.delete(entry.configId);
|
|
3396
|
+
onExited(entry, tunnel.status.errormsg || "Tunnel exited before a public URL was assigned");
|
|
3223
3397
|
}
|
|
3224
3398
|
}
|
|
3225
3399
|
}
|
|
3226
3400
|
completePendingStart(entry, urls) {
|
|
3227
|
-
if (this.
|
|
3401
|
+
if (this.pendingStarts.latest && !this.pendingStarts.isLatest(entry.configId)) {
|
|
3228
3402
|
this.pendingStarts.delete(entry.configId);
|
|
3229
3403
|
return;
|
|
3230
3404
|
}
|
|
3231
3405
|
this.pendingStarts.delete(entry.configId);
|
|
3232
|
-
this.latestPendingConfigId = void 0;
|
|
3233
3406
|
printer_default.stopSpinnerSuccess(`Tunnel started with config name: ${entry.configName}.`);
|
|
3234
3407
|
printer_default.info(import_picocolors3.default.cyanBright("Remote URLs:"));
|
|
3235
3408
|
(urls ?? []).forEach(
|
|
@@ -3237,28 +3410,27 @@ var init_websocket_printer = __esm({
|
|
|
3237
3410
|
);
|
|
3238
3411
|
}
|
|
3239
3412
|
cleanupExpiredPendingStarts() {
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
if (
|
|
3243
|
-
|
|
3413
|
+
this.pendingStarts.cleanupExpired((entry, wasLatest) => {
|
|
3414
|
+
const message = `Timed out while waiting for tunnel URL for config name: ${entry.configName}`;
|
|
3415
|
+
if (wasLatest) {
|
|
3416
|
+
printer_default.stopSpinnerFail(message);
|
|
3417
|
+
} else {
|
|
3418
|
+
printer_default.warn(message);
|
|
3244
3419
|
}
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
logger.warn("Pending websocket start entry expired", { configId, tunnelId: entry.tunnelId });
|
|
3248
|
-
}
|
|
3420
|
+
logger.warn("Pending websocket start entry expired", { configId: entry.configId, tunnelId: entry.tunnelId });
|
|
3421
|
+
});
|
|
3249
3422
|
}
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
return entry;
|
|
3423
|
+
cleanupExpiredPendingActions() {
|
|
3424
|
+
this.pendingActions.cleanupExpired((entry, wasLatest) => {
|
|
3425
|
+
const verb = entry.action === "restart" ? "restart" : "configuration update";
|
|
3426
|
+
const message = `Timed out while waiting for tunnel URL after ${verb} for config name: ${entry.configName}`;
|
|
3427
|
+
if (wasLatest) {
|
|
3428
|
+
printer_default.stopSpinnerFail(message);
|
|
3429
|
+
} else {
|
|
3430
|
+
printer_default.warn(message);
|
|
3259
3431
|
}
|
|
3260
|
-
|
|
3261
|
-
|
|
3432
|
+
logger.warn("Pending websocket action entry expired", { configId: entry.configId, tunnelId: entry.tunnelId, action: entry.action });
|
|
3433
|
+
});
|
|
3262
3434
|
}
|
|
3263
3435
|
resolveTunnelDetails(tunnelId, result) {
|
|
3264
3436
|
if (result && !isErrorResponse(result)) {
|
|
@@ -3267,7 +3439,7 @@ var init_websocket_printer = __esm({
|
|
|
3267
3439
|
configName: this.getConfigNameFromTunnel(result)
|
|
3268
3440
|
};
|
|
3269
3441
|
}
|
|
3270
|
-
return {
|
|
3442
|
+
return this.tunnelDetailsCache.get(tunnelId) ?? {
|
|
3271
3443
|
configId: tunnelId,
|
|
3272
3444
|
configName: tunnelId
|
|
3273
3445
|
};
|
|
@@ -3346,7 +3518,6 @@ var init_websocket_handlers = __esm({
|
|
|
3346
3518
|
WebSocketCommandHandler = class {
|
|
3347
3519
|
constructor(handler) {
|
|
3348
3520
|
this.tunnelHandler = handler ?? new TunnelOperations();
|
|
3349
|
-
remoteManagementWebSocketPrinter.setTunnelHandler(this.tunnelHandler);
|
|
3350
3521
|
}
|
|
3351
3522
|
safeParse(text) {
|
|
3352
3523
|
if (!text) return void 0;
|
|
@@ -3413,8 +3584,10 @@ var init_websocket_handlers = __esm({
|
|
|
3413
3584
|
}
|
|
3414
3585
|
}
|
|
3415
3586
|
async handleStopReq(req, raw) {
|
|
3587
|
+
let tunnelID;
|
|
3416
3588
|
try {
|
|
3417
3589
|
const dc = StopSchema.parse(raw);
|
|
3590
|
+
tunnelID = dc.tunnelID;
|
|
3418
3591
|
remoteManagementWebSocketPrinter.printStopRequested(dc.tunnelID);
|
|
3419
3592
|
const result = await this.tunnelHandler.handleStop(dc.tunnelID);
|
|
3420
3593
|
remoteManagementWebSocketPrinter.handleStopResult(dc.tunnelID, result);
|
|
@@ -3424,6 +3597,9 @@ var init_websocket_handlers = __esm({
|
|
|
3424
3597
|
printer_default.warn("Validation failed for stop request");
|
|
3425
3598
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
3426
3599
|
}
|
|
3600
|
+
if (tunnelID) {
|
|
3601
|
+
remoteManagementWebSocketPrinter.handleStopResult(tunnelID, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
3602
|
+
}
|
|
3427
3603
|
printer_default.warn(`Error in handleStopReq error: ${String(e)}`);
|
|
3428
3604
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
3429
3605
|
}
|
|
@@ -3443,8 +3619,10 @@ var init_websocket_handlers = __esm({
|
|
|
3443
3619
|
}
|
|
3444
3620
|
}
|
|
3445
3621
|
async handleRestartReq(req, raw) {
|
|
3622
|
+
let tunnelID;
|
|
3446
3623
|
try {
|
|
3447
3624
|
const dc = RestartSchema.parse(raw);
|
|
3625
|
+
tunnelID = dc.tunnelID;
|
|
3448
3626
|
remoteManagementWebSocketPrinter.printRestartRequested(dc.tunnelID);
|
|
3449
3627
|
const result = await this.tunnelHandler.handleRestart(dc.tunnelID, true);
|
|
3450
3628
|
remoteManagementWebSocketPrinter.handleRestartResult(dc.tunnelID, result);
|
|
@@ -3454,34 +3632,51 @@ var init_websocket_handlers = __esm({
|
|
|
3454
3632
|
printer_default.warn("Validation failed for restart request");
|
|
3455
3633
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
3456
3634
|
}
|
|
3635
|
+
if (tunnelID) {
|
|
3636
|
+
remoteManagementWebSocketPrinter.handleRestartResult(tunnelID, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
3637
|
+
}
|
|
3457
3638
|
printer_default.warn(`Error in handleRestartReq error: ${String(e)}`);
|
|
3458
3639
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
3459
3640
|
}
|
|
3460
3641
|
}
|
|
3461
3642
|
async handleUpdateConfigReq(req, raw) {
|
|
3643
|
+
let parsedConfig;
|
|
3462
3644
|
try {
|
|
3463
3645
|
const dc = UpdateConfigSchema.parse(raw);
|
|
3646
|
+
parsedConfig = dc.tunnelConfig;
|
|
3647
|
+
remoteManagementWebSocketPrinter.printUpdateConfigRequested(dc.tunnelConfig);
|
|
3464
3648
|
const result = await this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig, true);
|
|
3649
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(dc.tunnelConfig, result);
|
|
3465
3650
|
return this.wrapResponse(result, req);
|
|
3466
3651
|
} catch (e) {
|
|
3467
3652
|
if (e instanceof import_zod2.default.ZodError) {
|
|
3468
3653
|
printer_default.warn("Validation failed for updateconfig request");
|
|
3469
3654
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
3470
3655
|
}
|
|
3656
|
+
if (parsedConfig) {
|
|
3657
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(parsedConfig, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
3658
|
+
}
|
|
3471
3659
|
printer_default.warn(`Error in handleUpdateConfigReq error: ${String(e)}`);
|
|
3472
3660
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
3473
3661
|
}
|
|
3474
3662
|
}
|
|
3475
3663
|
async handleUpdateConfigV2Req(req, raw) {
|
|
3664
|
+
let parsedConfig;
|
|
3476
3665
|
try {
|
|
3477
3666
|
const dc = UpdateConfigV2Schema.parse(raw);
|
|
3667
|
+
parsedConfig = dc.tunnelConfig;
|
|
3668
|
+
remoteManagementWebSocketPrinter.printUpdateConfigRequested(dc.tunnelConfig);
|
|
3478
3669
|
const result = await this.tunnelHandler.handleUpdateConfigV2(dc.tunnelConfig, true);
|
|
3670
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(dc.tunnelConfig, result);
|
|
3479
3671
|
return this.wrapResponse(result, req);
|
|
3480
3672
|
} catch (e) {
|
|
3481
3673
|
if (e instanceof import_zod2.default.ZodError) {
|
|
3482
3674
|
printer_default.warn("Validation failed for update-config-v2 request");
|
|
3483
3675
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
3484
3676
|
}
|
|
3677
|
+
if (parsedConfig) {
|
|
3678
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(parsedConfig, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
3679
|
+
}
|
|
3485
3680
|
printer_default.warn(`Error in handleUpdateConfigV2Req error: ${String(e)}`);
|
|
3486
3681
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
3487
3682
|
}
|
|
@@ -3916,10 +4111,20 @@ function parseExtendedOptions(options, config, localServerTls) {
|
|
|
3916
4111
|
case "fullrequesturl":
|
|
3917
4112
|
config.originalRequestUrl = true;
|
|
3918
4113
|
break;
|
|
4114
|
+
case "haproxy":
|
|
4115
|
+
case "haproxy:v1":
|
|
4116
|
+
config.haProxy = import_pinggy4.HaProxyVersion.V1;
|
|
4117
|
+
break;
|
|
4118
|
+
case "haproxy:v2":
|
|
4119
|
+
config.haProxy = import_pinggy4.HaProxyVersion.V2;
|
|
4120
|
+
break;
|
|
3919
4121
|
default: {
|
|
3920
4122
|
if (value && (value.startsWith("localServerTls") || value.startsWith("localservertls"))) {
|
|
3921
4123
|
const parts = value.split(/:(.+)/);
|
|
3922
4124
|
localServerTls = parts[1] ? parts[1] : "";
|
|
4125
|
+
} else if (value && value.startsWith("haproxy:")) {
|
|
4126
|
+
printer_default.warn(`Invalid HAProxy version in "${opt}". Use x:haproxy, x:haproxy:v1 or x:haproxy:v2`);
|
|
4127
|
+
logger.warn(`Warning: Invalid HAProxy version in "${opt}". Use x:haproxy, x:haproxy:v1 or x:haproxy:v2`);
|
|
3923
4128
|
} else {
|
|
3924
4129
|
printer_default.warn(`Unknown extended option "${value}"`);
|
|
3925
4130
|
logger.warn(`Warning: Unknown extended option "${value}"`);
|
|
@@ -4021,11 +4226,12 @@ function isValidIpV6Cidr(input) {
|
|
|
4021
4226
|
}
|
|
4022
4227
|
return false;
|
|
4023
4228
|
}
|
|
4024
|
-
var import_net;
|
|
4229
|
+
var import_pinggy4, import_net;
|
|
4025
4230
|
var init_extendedOptions = __esm({
|
|
4026
4231
|
"src/cli/extendedOptions.ts"() {
|
|
4027
4232
|
"use strict";
|
|
4028
4233
|
init_cjs_shims();
|
|
4234
|
+
import_pinggy4 = require("@pinggy/pinggy");
|
|
4029
4235
|
import_net = require("net");
|
|
4030
4236
|
init_logger();
|
|
4031
4237
|
init_printer();
|
|
@@ -4062,7 +4268,7 @@ function parseUserAndDomain(str) {
|
|
|
4062
4268
|
const [user, domain] = str.split("@", 2);
|
|
4063
4269
|
if (isValidServerAddress(domain)) {
|
|
4064
4270
|
let processKeyword2 = function(keyword) {
|
|
4065
|
-
if ([
|
|
4271
|
+
if ([import_pinggy5.TunnelType.Http, import_pinggy5.TunnelType.Tcp, import_pinggy5.TunnelType.Tls, import_pinggy5.TunnelType.Udp, import_pinggy5.TunnelType.TlsTcp].includes(keyword)) {
|
|
4066
4272
|
type = keyword;
|
|
4067
4273
|
} else if (keyword === "force") {
|
|
4068
4274
|
forceFlag = true;
|
|
@@ -4150,7 +4356,7 @@ function parseUsers(positionalArgs, explicitToken) {
|
|
|
4150
4356
|
}
|
|
4151
4357
|
function parseType(finalConfig, values, inferredType) {
|
|
4152
4358
|
const t = inferredType || values.type;
|
|
4153
|
-
if (t ===
|
|
4359
|
+
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
4360
|
return t;
|
|
4155
4361
|
}
|
|
4156
4362
|
}
|
|
@@ -4248,7 +4454,7 @@ function parseAdditionalForwarding(forwarding) {
|
|
|
4248
4454
|
}
|
|
4249
4455
|
const firstPart = parsed[0];
|
|
4250
4456
|
const [hostPart] = firstPart.split("@");
|
|
4251
|
-
let protocol =
|
|
4457
|
+
let protocol = import_pinggy5.TunnelType.Http;
|
|
4252
4458
|
let remoteDomainRaw;
|
|
4253
4459
|
let remotePort = 0;
|
|
4254
4460
|
if (hostPart.includes("//")) {
|
|
@@ -4281,7 +4487,7 @@ function parseAdditionalForwarding(forwarding) {
|
|
|
4281
4487
|
if (!isValidServerAddress(remoteDomainRaw)) {
|
|
4282
4488
|
return new Error("invalid remote domain");
|
|
4283
4489
|
}
|
|
4284
|
-
protocol =
|
|
4490
|
+
protocol = import_pinggy5.TunnelType.Http;
|
|
4285
4491
|
remotePort = 0;
|
|
4286
4492
|
}
|
|
4287
4493
|
const localDomain = removeIPv6Brackets(parsed[2] || "localhost");
|
|
@@ -4311,7 +4517,7 @@ function parseReverseTunnelAddr(finalConfig, values, primaryType) {
|
|
|
4311
4517
|
if (parsed instanceof Error) return parsed;
|
|
4312
4518
|
forwardingData.push({
|
|
4313
4519
|
address: `${parsed.localDomain}:${parsed.localPort}`,
|
|
4314
|
-
type: primaryType ||
|
|
4520
|
+
type: primaryType || import_pinggy5.TunnelType.Http
|
|
4315
4521
|
});
|
|
4316
4522
|
} else if (slicedForwarding.length === 4) {
|
|
4317
4523
|
const parsed = parseAdditionalForwarding(forwarding);
|
|
@@ -4377,7 +4583,7 @@ function normalizeForwardingToArray(finalConfig, type) {
|
|
|
4377
4583
|
if (typeof fwd !== "string") {
|
|
4378
4584
|
return;
|
|
4379
4585
|
}
|
|
4380
|
-
finalConfig.forwarding = fwd.trim() === "" ? [] : [{ type: type ||
|
|
4586
|
+
finalConfig.forwarding = fwd.trim() === "" ? [] : [{ type: type || import_pinggy5.TunnelType.Http, address: fwd.trim() }];
|
|
4381
4587
|
}
|
|
4382
4588
|
function parseArgs2(finalConfig, remainingPositionals) {
|
|
4383
4589
|
let localserverTls = "";
|
|
@@ -4504,7 +4710,7 @@ function buildFinalConfig(values, positionals, baseConfig) {
|
|
|
4504
4710
|
storeJson(finalConfig, saveconf);
|
|
4505
4711
|
return finalConfig;
|
|
4506
4712
|
}
|
|
4507
|
-
var
|
|
4713
|
+
var import_pinggy5, import_fs6, import_path6, import_net2, domainRegex, KEYWORDS, VALID_PROTOCOLS;
|
|
4508
4714
|
var init_buildConfig = __esm({
|
|
4509
4715
|
"src/cli/buildConfig.ts"() {
|
|
4510
4716
|
"use strict";
|
|
@@ -4513,21 +4719,21 @@ var init_buildConfig = __esm({
|
|
|
4513
4719
|
init_extendedOptions();
|
|
4514
4720
|
init_logger();
|
|
4515
4721
|
init_util();
|
|
4516
|
-
|
|
4722
|
+
import_pinggy5 = require("@pinggy/pinggy");
|
|
4517
4723
|
import_fs6 = __toESM(require("fs"), 1);
|
|
4518
4724
|
import_path6 = __toESM(require("path"), 1);
|
|
4519
4725
|
import_net2 = require("net");
|
|
4520
4726
|
domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
|
|
4521
4727
|
KEYWORDS = /* @__PURE__ */ new Set([
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4728
|
+
import_pinggy5.TunnelType.Http,
|
|
4729
|
+
import_pinggy5.TunnelType.Tcp,
|
|
4730
|
+
import_pinggy5.TunnelType.Tls,
|
|
4731
|
+
import_pinggy5.TunnelType.Udp,
|
|
4732
|
+
import_pinggy5.TunnelType.TlsTcp,
|
|
4527
4733
|
"force",
|
|
4528
4734
|
"qr"
|
|
4529
4735
|
]);
|
|
4530
|
-
VALID_PROTOCOLS = [
|
|
4736
|
+
VALID_PROTOCOLS = [import_pinggy5.TunnelType.Http, import_pinggy5.TunnelType.Tcp, import_pinggy5.TunnelType.Udp, import_pinggy5.TunnelType.Tls, import_pinggy5.TunnelType.TlsTcp];
|
|
4531
4737
|
}
|
|
4532
4738
|
});
|
|
4533
4739
|
|
|
@@ -5111,7 +5317,7 @@ var init_webDebuggerConnection = __esm({
|
|
|
5111
5317
|
});
|
|
5112
5318
|
|
|
5113
5319
|
// src/tui/ink/asciArt.ts
|
|
5114
|
-
var asciiArtPinggyLogo;
|
|
5320
|
+
var asciiArtPinggyLogo, asciiArtPinggyLogoCompact, asciiArtPinggyLogoMedium;
|
|
5115
5321
|
var init_asciArt = __esm({
|
|
5116
5322
|
"src/tui/ink/asciArt.ts"() {
|
|
5117
5323
|
"use strict";
|
|
@@ -5123,6 +5329,13 @@ var init_asciArt = __esm({
|
|
|
5123
5329
|
\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
5330
|
\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
5331
|
\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 `;
|
|
5332
|
+
asciiArtPinggyLogoCompact = "P I N G G Y";
|
|
5333
|
+
asciiArtPinggyLogoMedium = [
|
|
5334
|
+
" ___ ___ _ _ ___ _____ __",
|
|
5335
|
+
"| _ \\_ _| \\| |/ __|/ __\\ \\ / /",
|
|
5336
|
+
"| _/| || .` | (_ | (_ |\\ V /",
|
|
5337
|
+
"|_| |___|_|\\_|\\___|\\___| |_|"
|
|
5338
|
+
].join("\n");
|
|
5126
5339
|
}
|
|
5127
5340
|
});
|
|
5128
5341
|
|
|
@@ -5135,6 +5348,54 @@ function colorizeGradient(text) {
|
|
|
5135
5348
|
return `{${color}-fg}${line}{/${color}-fg}`;
|
|
5136
5349
|
}).join("\n");
|
|
5137
5350
|
}
|
|
5351
|
+
function colorizeGradientHorizontal(text) {
|
|
5352
|
+
const colors = ["red", "yellow", "green", "cyan", "blue", "magenta"];
|
|
5353
|
+
let colorIndex = 0;
|
|
5354
|
+
return [...text].map((char) => {
|
|
5355
|
+
if (char === " ") return char;
|
|
5356
|
+
const color = colors[colorIndex % colors.length];
|
|
5357
|
+
colorIndex++;
|
|
5358
|
+
return `{${color}-fg}${char}{/${color}-fg}`;
|
|
5359
|
+
}).join("");
|
|
5360
|
+
}
|
|
5361
|
+
function selectLogo(width, height) {
|
|
5362
|
+
const widthTier = width >= FULL_LOGO_THRESHOLD ? 2 : width >= MEDIUM_LOGO_THRESHOLD ? 1 : 0;
|
|
5363
|
+
const heightTier = height >= FULL_LOGO_HEIGHT_THRESHOLD ? 2 : height >= MEDIUM_LOGO_HEIGHT_THRESHOLD ? 1 : 0;
|
|
5364
|
+
const tier = Math.min(widthTier, heightTier);
|
|
5365
|
+
if (tier === 2) {
|
|
5366
|
+
return { content: colorizeGradient(asciiArtPinggyLogo), boxHeight: 7 };
|
|
5367
|
+
}
|
|
5368
|
+
if (tier === 1) {
|
|
5369
|
+
return { content: colorizeGradient(asciiArtPinggyLogoMedium), boxHeight: 5 };
|
|
5370
|
+
}
|
|
5371
|
+
return {
|
|
5372
|
+
content: `{bold}${colorizeGradientHorizontal(asciiArtPinggyLogoCompact)}{/bold}`,
|
|
5373
|
+
boxHeight: 1
|
|
5374
|
+
};
|
|
5375
|
+
}
|
|
5376
|
+
function wrapText(text, width) {
|
|
5377
|
+
const lines = [];
|
|
5378
|
+
let current = "";
|
|
5379
|
+
for (const word of text.split(/\s+/).filter(Boolean)) {
|
|
5380
|
+
const candidate = current ? `${current} ${word}` : word;
|
|
5381
|
+
if (candidate.length <= width) {
|
|
5382
|
+
current = candidate;
|
|
5383
|
+
continue;
|
|
5384
|
+
}
|
|
5385
|
+
if (current) {
|
|
5386
|
+
lines.push(current);
|
|
5387
|
+
current = "";
|
|
5388
|
+
}
|
|
5389
|
+
let rest = word;
|
|
5390
|
+
while (rest.length > width) {
|
|
5391
|
+
lines.push(rest.slice(0, width));
|
|
5392
|
+
rest = rest.slice(width);
|
|
5393
|
+
}
|
|
5394
|
+
current = rest;
|
|
5395
|
+
}
|
|
5396
|
+
if (current) lines.push(current);
|
|
5397
|
+
return lines.length ? lines : [""];
|
|
5398
|
+
}
|
|
5138
5399
|
function createWarningUI(screen) {
|
|
5139
5400
|
return import_blessed.default.box({
|
|
5140
5401
|
parent: screen,
|
|
@@ -5161,21 +5422,26 @@ function createFullUI(screen, urls, greet, tunnelConfig) {
|
|
|
5161
5422
|
height: "100%",
|
|
5162
5423
|
padding: 1
|
|
5163
5424
|
});
|
|
5425
|
+
const { content: logoContent, boxHeight: logoHeight } = selectLogo(
|
|
5426
|
+
screen.width,
|
|
5427
|
+
screen.height
|
|
5428
|
+
);
|
|
5164
5429
|
const logoBox = import_blessed.default.box({
|
|
5165
5430
|
parent: mainContainer,
|
|
5166
5431
|
top: 0,
|
|
5167
5432
|
left: 0,
|
|
5168
5433
|
width: "100%",
|
|
5169
|
-
height:
|
|
5170
|
-
content:
|
|
5434
|
+
height: logoHeight,
|
|
5435
|
+
content: logoContent,
|
|
5171
5436
|
tags: true
|
|
5172
5437
|
});
|
|
5438
|
+
const contentTop = logoHeight + 1;
|
|
5173
5439
|
const contentBox = import_blessed.default.box({
|
|
5174
5440
|
parent: mainContainer,
|
|
5175
|
-
top:
|
|
5441
|
+
top: contentTop,
|
|
5176
5442
|
left: 0,
|
|
5177
5443
|
width: "100%-2",
|
|
5178
|
-
height:
|
|
5444
|
+
height: `100%-${contentTop + 2}`,
|
|
5179
5445
|
padding: 0,
|
|
5180
5446
|
border: {
|
|
5181
5447
|
type: "line"
|
|
@@ -5188,20 +5454,24 @@ function createFullUI(screen, urls, greet, tunnelConfig) {
|
|
|
5188
5454
|
});
|
|
5189
5455
|
let greetHeight = 0;
|
|
5190
5456
|
if (greet) {
|
|
5191
|
-
const
|
|
5457
|
+
const contentInnerWidth = screen.width - 6;
|
|
5458
|
+
const greetWidth = Math.max(20, Math.floor(contentInnerWidth * 0.6));
|
|
5459
|
+
const greetLines = wrapText(greet, greetWidth);
|
|
5460
|
+
const greetBoxHeight = greetLines.length + 1;
|
|
5461
|
+
import_blessed.default.box({
|
|
5192
5462
|
parent: contentBox,
|
|
5193
5463
|
top: 0,
|
|
5194
5464
|
left: "center",
|
|
5195
|
-
width:
|
|
5196
|
-
height:
|
|
5197
|
-
content: `{bold}${
|
|
5465
|
+
width: greetWidth,
|
|
5466
|
+
height: greetBoxHeight,
|
|
5467
|
+
content: greetLines.map((line) => `{bold}${line}{/bold}`).join("\n"),
|
|
5198
5468
|
tags: true,
|
|
5199
5469
|
align: "center",
|
|
5200
5470
|
style: {
|
|
5201
5471
|
fg: "green"
|
|
5202
5472
|
}
|
|
5203
5473
|
});
|
|
5204
|
-
greetHeight =
|
|
5474
|
+
greetHeight = greetBoxHeight;
|
|
5205
5475
|
}
|
|
5206
5476
|
const upperSectionTop = greetHeight > 0 ? greetHeight : 0;
|
|
5207
5477
|
const upperSection = import_blessed.default.box({
|
|
@@ -5295,21 +5565,22 @@ function createSimpleUI(screen, urls, greet) {
|
|
|
5295
5565
|
});
|
|
5296
5566
|
let currentTop = 0;
|
|
5297
5567
|
if (greet) {
|
|
5568
|
+
const greetWidth = Math.max(20, Math.floor((screen.width - 2) * 0.9));
|
|
5569
|
+
const greetLines = wrapText(greet, greetWidth);
|
|
5298
5570
|
import_blessed.default.box({
|
|
5299
5571
|
parent: mainContainer,
|
|
5300
5572
|
top: currentTop,
|
|
5301
5573
|
left: "center",
|
|
5302
|
-
width:
|
|
5303
|
-
height:
|
|
5304
|
-
content: `{bold}${
|
|
5574
|
+
width: greetWidth,
|
|
5575
|
+
height: greetLines.length,
|
|
5576
|
+
content: greetLines.map((line) => `{bold}${line}{/bold}`).join("\n"),
|
|
5305
5577
|
tags: true,
|
|
5306
5578
|
align: "center",
|
|
5307
5579
|
style: {
|
|
5308
5580
|
fg: "green"
|
|
5309
5581
|
}
|
|
5310
5582
|
});
|
|
5311
|
-
|
|
5312
|
-
currentTop += Math.max(lines, 1) + 1;
|
|
5583
|
+
currentTop += greetLines.length + 1;
|
|
5313
5584
|
}
|
|
5314
5585
|
const urlsBox = import_blessed.default.box({
|
|
5315
5586
|
parent: mainContainer,
|
|
@@ -5348,7 +5619,7 @@ function createSimpleUI(screen, urls, greet) {
|
|
|
5348
5619
|
footerBox
|
|
5349
5620
|
};
|
|
5350
5621
|
}
|
|
5351
|
-
var import_blessed, MIN_WIDTH_WARNING, SIMPLE_LAYOUT_THRESHOLD;
|
|
5622
|
+
var import_blessed, MIN_WIDTH_WARNING, SIMPLE_LAYOUT_THRESHOLD, MEDIUM_LOGO_THRESHOLD, FULL_LOGO_THRESHOLD, MEDIUM_LOGO_HEIGHT_THRESHOLD, FULL_LOGO_HEIGHT_THRESHOLD;
|
|
5352
5623
|
var init_UIComponents = __esm({
|
|
5353
5624
|
"src/tui/blessed/components/UIComponents.ts"() {
|
|
5354
5625
|
"use strict";
|
|
@@ -5357,6 +5628,10 @@ var init_UIComponents = __esm({
|
|
|
5357
5628
|
init_asciArt();
|
|
5358
5629
|
MIN_WIDTH_WARNING = 60;
|
|
5359
5630
|
SIMPLE_LAYOUT_THRESHOLD = 80;
|
|
5631
|
+
MEDIUM_LOGO_THRESHOLD = 100;
|
|
5632
|
+
FULL_LOGO_THRESHOLD = 120;
|
|
5633
|
+
MEDIUM_LOGO_HEIGHT_THRESHOLD = 26;
|
|
5634
|
+
FULL_LOGO_HEIGHT_THRESHOLD = 34;
|
|
5360
5635
|
}
|
|
5361
5636
|
});
|
|
5362
5637
|
|
|
@@ -8322,7 +8597,7 @@ var init_ipcServer = __esm({
|
|
|
8322
8597
|
},
|
|
8323
8598
|
[Route.RestartTunnel]: async (req) => {
|
|
8324
8599
|
if (!req.tunnelid) throw new Error("Missing 'tunnelid' field");
|
|
8325
|
-
return await this.ops.handleRestart(req.tunnelid);
|
|
8600
|
+
return await this.ops.handleRestart(req.tunnelid, req.noWait);
|
|
8326
8601
|
},
|
|
8327
8602
|
[Route.UpdateConfig]: async (req) => {
|
|
8328
8603
|
if (!req.config) throw new Error("Missing 'config' field");
|
|
@@ -9817,9 +10092,9 @@ var init_tunnelClient = __esm({
|
|
|
9817
10092
|
this.assertClient();
|
|
9818
10093
|
return this.ipc.getTunnel(tunnelId);
|
|
9819
10094
|
}
|
|
9820
|
-
async handleRestart(tunnelId) {
|
|
10095
|
+
async handleRestart(tunnelId, noWait) {
|
|
9821
10096
|
this.assertClient();
|
|
9822
|
-
return this.ipc.restartTunnel(tunnelId);
|
|
10097
|
+
return this.ipc.restartTunnel(tunnelId, noWait);
|
|
9823
10098
|
}
|
|
9824
10099
|
async shutdown() {
|
|
9825
10100
|
this.assertClient();
|