pinggy 0.5.2 → 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-LFK74BMU.js → chunk-NBR2L6LW.js} +40 -12
- package/dist/{chunk-7G6SJEEA.js → chunk-P7WNBN7F.js} +1 -1
- package/dist/{chunk-DLNUDW6G.js → chunk-PQSLSIDA.js} +67 -13
- package/dist/{chunk-C3ZMLIC4.js → chunk-UX64XCC2.js} +251 -77
- package/dist/{chunk-BFARGPGP.js → chunk-YTJEV27N.js} +12 -2
- package/dist/{chunk-WM7I57AA.js → chunk-YWXADADB.js} +1 -1
- package/dist/{configStore-HP3NXPH4.js → configStore-2AZRILVC.js} +2 -2
- package/dist/{daemonChild-6QA4AVAN.js → daemonChild-ZACX7V6J.js} +11 -6
- package/dist/index.cjs +459 -120
- package/dist/index.d.cts +65 -19
- package/dist/index.d.ts +65 -19
- package/dist/index.js +10 -6
- package/dist/{ipcClient-LZQCCNMR.js → ipcClient-XRL2WUUM.js} +1 -1
- package/dist/{main-K6ZEWZQ7.js → main-KG3Q4LXI.js} +9 -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)
|
|
@@ -2717,7 +2778,7 @@ var init_handler = __esm({
|
|
|
2717
2778
|
});
|
|
2718
2779
|
|
|
2719
2780
|
// src/daemon/ipc/ipcRoutes.ts
|
|
2720
|
-
var Route, SessionMode;
|
|
2781
|
+
var Route, DaemonHost, ShutdownStatus, SessionMode;
|
|
2721
2782
|
var init_ipcRoutes = __esm({
|
|
2722
2783
|
"src/daemon/ipc/ipcRoutes.ts"() {
|
|
2723
2784
|
"use strict";
|
|
@@ -2741,6 +2802,14 @@ var init_ipcRoutes = __esm({
|
|
|
2741
2802
|
SetTunnelLogging: "POST /config/tunnel-logging",
|
|
2742
2803
|
GetLogPaths: "GET /logs/paths"
|
|
2743
2804
|
};
|
|
2805
|
+
DaemonHost = {
|
|
2806
|
+
CLI: "cli",
|
|
2807
|
+
APP: "app"
|
|
2808
|
+
};
|
|
2809
|
+
ShutdownStatus = {
|
|
2810
|
+
ShuttingDown: "shutting_down",
|
|
2811
|
+
RefusedInApp: "refused_in_app"
|
|
2812
|
+
};
|
|
2744
2813
|
SessionMode = {
|
|
2745
2814
|
Foreground: "foreground",
|
|
2746
2815
|
Detached: "detached"
|
|
@@ -2787,8 +2856,8 @@ var init_ipcClient = __esm({
|
|
|
2787
2856
|
async stopTunnel(tunnelid) {
|
|
2788
2857
|
return this.call(Route.StopTunnel, { tunnelid });
|
|
2789
2858
|
}
|
|
2790
|
-
async restartTunnel(tunnelid) {
|
|
2791
|
-
return this.call(Route.RestartTunnel, { tunnelid });
|
|
2859
|
+
async restartTunnel(tunnelid, noWait) {
|
|
2860
|
+
return this.call(Route.RestartTunnel, { tunnelid, noWait });
|
|
2792
2861
|
}
|
|
2793
2862
|
// v1 operations (used by remote management via daemon)
|
|
2794
2863
|
async startTunnelV1(config, mode, noWait) {
|
|
@@ -2970,6 +3039,7 @@ function printHelpMessage() {
|
|
|
2970
3039
|
console.log(" x:noreverseproxy Disable built-in reverse-proxy header injection");
|
|
2971
3040
|
console.log(" x:localservertls:host Connect to local HTTPS server with SNI");
|
|
2972
3041
|
console.log(" x:passpreflight Pass CORS preflight requests unchanged");
|
|
3042
|
+
console.log(" x:haproxy[:v1|:v2] Enable HAProxy PROXY protocol (default v1)");
|
|
2973
3043
|
console.log(" a:Key:Val Add header");
|
|
2974
3044
|
console.log(" u:Key:Val Update header");
|
|
2975
3045
|
console.log(" r:Key Remove header");
|
|
@@ -3095,7 +3165,7 @@ var init_parseArgs = __esm({
|
|
|
3095
3165
|
});
|
|
3096
3166
|
|
|
3097
3167
|
// src/remote_management/websocket_printer.ts
|
|
3098
|
-
var import_picocolors3,
|
|
3168
|
+
var import_picocolors3, PENDING_TIMEOUT_MS, PendingTracker, RemoteManagementWebSocketPrinter, remoteManagementWebSocketPrinter;
|
|
3099
3169
|
var init_websocket_printer = __esm({
|
|
3100
3170
|
"src/remote_management/websocket_printer.ts"() {
|
|
3101
3171
|
"use strict";
|
|
@@ -3104,13 +3174,71 @@ var init_websocket_printer = __esm({
|
|
|
3104
3174
|
init_types();
|
|
3105
3175
|
init_printer();
|
|
3106
3176
|
import_picocolors3 = __toESM(require("picocolors"), 1);
|
|
3107
|
-
|
|
3108
|
-
|
|
3177
|
+
PENDING_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3178
|
+
PendingTracker = class {
|
|
3109
3179
|
constructor() {
|
|
3110
|
-
this.
|
|
3180
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
3111
3181
|
}
|
|
3112
|
-
|
|
3113
|
-
this.
|
|
3182
|
+
queue(entry) {
|
|
3183
|
+
this.latestConfigId = entry.configId;
|
|
3184
|
+
this.entries.set(entry.configId, entry);
|
|
3185
|
+
}
|
|
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();
|
|
3114
3242
|
}
|
|
3115
3243
|
queueStart(config) {
|
|
3116
3244
|
this.cleanupExpiredPendingStarts();
|
|
@@ -3119,24 +3247,23 @@ var init_websocket_printer = __esm({
|
|
|
3119
3247
|
configName: this.getConfigNameFromRequest(config),
|
|
3120
3248
|
queuedAt: Date.now()
|
|
3121
3249
|
};
|
|
3122
|
-
this.
|
|
3123
|
-
this.pendingStarts.set(entry.configId, entry);
|
|
3250
|
+
this.pendingStarts.queue(entry);
|
|
3124
3251
|
printer_default.startSpinner("Starting tunnel with config name: " + entry.configName);
|
|
3125
3252
|
}
|
|
3126
3253
|
failQueuedStart(config, reason) {
|
|
3127
3254
|
const configId = this.getConfigIdFromRequest(config);
|
|
3128
3255
|
const pending = this.pendingStarts.get(configId);
|
|
3129
3256
|
const configName = pending?.configName || this.getConfigNameFromRequest(config);
|
|
3257
|
+
const wasLatest = this.pendingStarts.isLatest(configId);
|
|
3130
3258
|
this.pendingStarts.delete(configId);
|
|
3131
|
-
if (
|
|
3132
|
-
this.latestPendingConfigId = void 0;
|
|
3259
|
+
if (wasLatest) {
|
|
3133
3260
|
printer_default.stopSpinnerFail(`Failed to start tunnel with config name: ${configName}. ${reason}`);
|
|
3134
3261
|
}
|
|
3135
3262
|
}
|
|
3136
3263
|
handleStartResult(config, result) {
|
|
3137
3264
|
this.cleanupExpiredPendingStarts();
|
|
3138
3265
|
const requestedConfigId = this.getConfigIdFromRequest(config);
|
|
3139
|
-
if (this.
|
|
3266
|
+
if (this.pendingStarts.latest && !this.pendingStarts.isLatest(requestedConfigId)) {
|
|
3140
3267
|
this.pendingStarts.delete(requestedConfigId);
|
|
3141
3268
|
return;
|
|
3142
3269
|
}
|
|
@@ -3144,20 +3271,20 @@ var init_websocket_printer = __esm({
|
|
|
3144
3271
|
this.failQueuedStart(config, result.message);
|
|
3145
3272
|
return;
|
|
3146
3273
|
}
|
|
3147
|
-
const configId = this.getConfigIdFromTunnel(result);
|
|
3148
3274
|
const pending = this.pendingStarts.get(requestedConfigId) || {
|
|
3149
3275
|
configId: requestedConfigId,
|
|
3150
3276
|
configName: this.getConfigNameFromRequest(config),
|
|
3151
3277
|
queuedAt: Date.now()
|
|
3152
3278
|
};
|
|
3153
3279
|
pending.tunnelId = result.tunnelid;
|
|
3154
|
-
this.pendingStarts.
|
|
3280
|
+
this.pendingStarts.upsert(pending);
|
|
3155
3281
|
if (result.remoteurls.length > 0) {
|
|
3156
3282
|
this.completePendingStart(pending, result.remoteurls);
|
|
3157
3283
|
}
|
|
3158
3284
|
}
|
|
3159
3285
|
printStopRequested(tunnelId) {
|
|
3160
3286
|
const details = this.resolveTunnelDetails(tunnelId);
|
|
3287
|
+
this.clearPendingAction(details.configId, tunnelId);
|
|
3161
3288
|
printer_default.startSpinner("Stopping tunnel with config name: " + details.configName);
|
|
3162
3289
|
}
|
|
3163
3290
|
handleStopResult(tunnelId, result) {
|
|
@@ -3171,6 +3298,7 @@ var init_websocket_printer = __esm({
|
|
|
3171
3298
|
}
|
|
3172
3299
|
printRestartRequested(tunnelId) {
|
|
3173
3300
|
const details = this.resolveTunnelDetails(tunnelId);
|
|
3301
|
+
this.clearPendingAction(details.configId, tunnelId);
|
|
3174
3302
|
printer_default.startSpinner("Restarting tunnel with config name: " + details.configName);
|
|
3175
3303
|
}
|
|
3176
3304
|
handleRestartResult(tunnelId, result) {
|
|
@@ -3180,48 +3308,101 @@ var init_websocket_printer = __esm({
|
|
|
3180
3308
|
printer_default.stopSpinnerFail("Failed to restart tunnel with config name: " + details.configName);
|
|
3181
3309
|
return;
|
|
3182
3310
|
}
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
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;
|
|
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;
|
|
3189
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)));
|
|
3190
3347
|
}
|
|
3191
3348
|
monitorList(result) {
|
|
3192
3349
|
this.cleanupExpiredPendingStarts();
|
|
3193
|
-
|
|
3350
|
+
this.cleanupExpiredPendingActions();
|
|
3351
|
+
if (!Array.isArray(result)) {
|
|
3194
3352
|
return;
|
|
3195
3353
|
}
|
|
3196
3354
|
for (const tunnel of result) {
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
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}`);
|
|
3200
3376
|
}
|
|
3201
|
-
|
|
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)) {
|
|
3202
3386
|
continue;
|
|
3203
3387
|
}
|
|
3204
|
-
|
|
3205
|
-
|
|
3388
|
+
entry.tunnelId = tunnel.tunnelid;
|
|
3389
|
+
tracker.upsert(entry);
|
|
3206
3390
|
if (tunnel.remoteurls.length > 0) {
|
|
3207
|
-
|
|
3391
|
+
onComplete(entry, tunnel.remoteurls);
|
|
3208
3392
|
continue;
|
|
3209
3393
|
}
|
|
3210
3394
|
if (tunnel.status.state === "exited" /* Exited */) {
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
this.latestPendingConfigId = void 0;
|
|
3214
|
-
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");
|
|
3215
3397
|
}
|
|
3216
3398
|
}
|
|
3217
3399
|
}
|
|
3218
3400
|
completePendingStart(entry, urls) {
|
|
3219
|
-
if (this.
|
|
3401
|
+
if (this.pendingStarts.latest && !this.pendingStarts.isLatest(entry.configId)) {
|
|
3220
3402
|
this.pendingStarts.delete(entry.configId);
|
|
3221
3403
|
return;
|
|
3222
3404
|
}
|
|
3223
3405
|
this.pendingStarts.delete(entry.configId);
|
|
3224
|
-
this.latestPendingConfigId = void 0;
|
|
3225
3406
|
printer_default.stopSpinnerSuccess(`Tunnel started with config name: ${entry.configName}.`);
|
|
3226
3407
|
printer_default.info(import_picocolors3.default.cyanBright("Remote URLs:"));
|
|
3227
3408
|
(urls ?? []).forEach(
|
|
@@ -3229,28 +3410,27 @@ var init_websocket_printer = __esm({
|
|
|
3229
3410
|
);
|
|
3230
3411
|
}
|
|
3231
3412
|
cleanupExpiredPendingStarts() {
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
if (
|
|
3235
|
-
|
|
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);
|
|
3236
3419
|
}
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
logger.warn("Pending websocket start entry expired", { configId, tunnelId: entry.tunnelId });
|
|
3240
|
-
}
|
|
3420
|
+
logger.warn("Pending websocket start entry expired", { configId: entry.configId, tunnelId: entry.tunnelId });
|
|
3421
|
+
});
|
|
3241
3422
|
}
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
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);
|
|
3251
3431
|
}
|
|
3252
|
-
|
|
3253
|
-
|
|
3432
|
+
logger.warn("Pending websocket action entry expired", { configId: entry.configId, tunnelId: entry.tunnelId, action: entry.action });
|
|
3433
|
+
});
|
|
3254
3434
|
}
|
|
3255
3435
|
resolveTunnelDetails(tunnelId, result) {
|
|
3256
3436
|
if (result && !isErrorResponse(result)) {
|
|
@@ -3259,7 +3439,7 @@ var init_websocket_printer = __esm({
|
|
|
3259
3439
|
configName: this.getConfigNameFromTunnel(result)
|
|
3260
3440
|
};
|
|
3261
3441
|
}
|
|
3262
|
-
return {
|
|
3442
|
+
return this.tunnelDetailsCache.get(tunnelId) ?? {
|
|
3263
3443
|
configId: tunnelId,
|
|
3264
3444
|
configName: tunnelId
|
|
3265
3445
|
};
|
|
@@ -3338,7 +3518,6 @@ var init_websocket_handlers = __esm({
|
|
|
3338
3518
|
WebSocketCommandHandler = class {
|
|
3339
3519
|
constructor(handler) {
|
|
3340
3520
|
this.tunnelHandler = handler ?? new TunnelOperations();
|
|
3341
|
-
remoteManagementWebSocketPrinter.setTunnelHandler(this.tunnelHandler);
|
|
3342
3521
|
}
|
|
3343
3522
|
safeParse(text) {
|
|
3344
3523
|
if (!text) return void 0;
|
|
@@ -3405,8 +3584,10 @@ var init_websocket_handlers = __esm({
|
|
|
3405
3584
|
}
|
|
3406
3585
|
}
|
|
3407
3586
|
async handleStopReq(req, raw) {
|
|
3587
|
+
let tunnelID;
|
|
3408
3588
|
try {
|
|
3409
3589
|
const dc = StopSchema.parse(raw);
|
|
3590
|
+
tunnelID = dc.tunnelID;
|
|
3410
3591
|
remoteManagementWebSocketPrinter.printStopRequested(dc.tunnelID);
|
|
3411
3592
|
const result = await this.tunnelHandler.handleStop(dc.tunnelID);
|
|
3412
3593
|
remoteManagementWebSocketPrinter.handleStopResult(dc.tunnelID, result);
|
|
@@ -3416,6 +3597,9 @@ var init_websocket_handlers = __esm({
|
|
|
3416
3597
|
printer_default.warn("Validation failed for stop request");
|
|
3417
3598
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
3418
3599
|
}
|
|
3600
|
+
if (tunnelID) {
|
|
3601
|
+
remoteManagementWebSocketPrinter.handleStopResult(tunnelID, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
3602
|
+
}
|
|
3419
3603
|
printer_default.warn(`Error in handleStopReq error: ${String(e)}`);
|
|
3420
3604
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
3421
3605
|
}
|
|
@@ -3435,8 +3619,10 @@ var init_websocket_handlers = __esm({
|
|
|
3435
3619
|
}
|
|
3436
3620
|
}
|
|
3437
3621
|
async handleRestartReq(req, raw) {
|
|
3622
|
+
let tunnelID;
|
|
3438
3623
|
try {
|
|
3439
3624
|
const dc = RestartSchema.parse(raw);
|
|
3625
|
+
tunnelID = dc.tunnelID;
|
|
3440
3626
|
remoteManagementWebSocketPrinter.printRestartRequested(dc.tunnelID);
|
|
3441
3627
|
const result = await this.tunnelHandler.handleRestart(dc.tunnelID, true);
|
|
3442
3628
|
remoteManagementWebSocketPrinter.handleRestartResult(dc.tunnelID, result);
|
|
@@ -3446,34 +3632,51 @@ var init_websocket_handlers = __esm({
|
|
|
3446
3632
|
printer_default.warn("Validation failed for restart request");
|
|
3447
3633
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
3448
3634
|
}
|
|
3635
|
+
if (tunnelID) {
|
|
3636
|
+
remoteManagementWebSocketPrinter.handleRestartResult(tunnelID, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
3637
|
+
}
|
|
3449
3638
|
printer_default.warn(`Error in handleRestartReq error: ${String(e)}`);
|
|
3450
3639
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
3451
3640
|
}
|
|
3452
3641
|
}
|
|
3453
3642
|
async handleUpdateConfigReq(req, raw) {
|
|
3643
|
+
let parsedConfig;
|
|
3454
3644
|
try {
|
|
3455
3645
|
const dc = UpdateConfigSchema.parse(raw);
|
|
3646
|
+
parsedConfig = dc.tunnelConfig;
|
|
3647
|
+
remoteManagementWebSocketPrinter.printUpdateConfigRequested(dc.tunnelConfig);
|
|
3456
3648
|
const result = await this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig, true);
|
|
3649
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(dc.tunnelConfig, result);
|
|
3457
3650
|
return this.wrapResponse(result, req);
|
|
3458
3651
|
} catch (e) {
|
|
3459
3652
|
if (e instanceof import_zod2.default.ZodError) {
|
|
3460
3653
|
printer_default.warn("Validation failed for updateconfig request");
|
|
3461
3654
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
3462
3655
|
}
|
|
3656
|
+
if (parsedConfig) {
|
|
3657
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(parsedConfig, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
3658
|
+
}
|
|
3463
3659
|
printer_default.warn(`Error in handleUpdateConfigReq error: ${String(e)}`);
|
|
3464
3660
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
3465
3661
|
}
|
|
3466
3662
|
}
|
|
3467
3663
|
async handleUpdateConfigV2Req(req, raw) {
|
|
3664
|
+
let parsedConfig;
|
|
3468
3665
|
try {
|
|
3469
3666
|
const dc = UpdateConfigV2Schema.parse(raw);
|
|
3667
|
+
parsedConfig = dc.tunnelConfig;
|
|
3668
|
+
remoteManagementWebSocketPrinter.printUpdateConfigRequested(dc.tunnelConfig);
|
|
3470
3669
|
const result = await this.tunnelHandler.handleUpdateConfigV2(dc.tunnelConfig, true);
|
|
3670
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(dc.tunnelConfig, result);
|
|
3471
3671
|
return this.wrapResponse(result, req);
|
|
3472
3672
|
} catch (e) {
|
|
3473
3673
|
if (e instanceof import_zod2.default.ZodError) {
|
|
3474
3674
|
printer_default.warn("Validation failed for update-config-v2 request");
|
|
3475
3675
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
3476
3676
|
}
|
|
3677
|
+
if (parsedConfig) {
|
|
3678
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(parsedConfig, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
3679
|
+
}
|
|
3477
3680
|
printer_default.warn(`Error in handleUpdateConfigV2Req error: ${String(e)}`);
|
|
3478
3681
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
3479
3682
|
}
|
|
@@ -3908,10 +4111,20 @@ function parseExtendedOptions(options, config, localServerTls) {
|
|
|
3908
4111
|
case "fullrequesturl":
|
|
3909
4112
|
config.originalRequestUrl = true;
|
|
3910
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;
|
|
3911
4121
|
default: {
|
|
3912
4122
|
if (value && (value.startsWith("localServerTls") || value.startsWith("localservertls"))) {
|
|
3913
4123
|
const parts = value.split(/:(.+)/);
|
|
3914
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`);
|
|
3915
4128
|
} else {
|
|
3916
4129
|
printer_default.warn(`Unknown extended option "${value}"`);
|
|
3917
4130
|
logger.warn(`Warning: Unknown extended option "${value}"`);
|
|
@@ -4013,11 +4226,12 @@ function isValidIpV6Cidr(input) {
|
|
|
4013
4226
|
}
|
|
4014
4227
|
return false;
|
|
4015
4228
|
}
|
|
4016
|
-
var import_net;
|
|
4229
|
+
var import_pinggy4, import_net;
|
|
4017
4230
|
var init_extendedOptions = __esm({
|
|
4018
4231
|
"src/cli/extendedOptions.ts"() {
|
|
4019
4232
|
"use strict";
|
|
4020
4233
|
init_cjs_shims();
|
|
4234
|
+
import_pinggy4 = require("@pinggy/pinggy");
|
|
4021
4235
|
import_net = require("net");
|
|
4022
4236
|
init_logger();
|
|
4023
4237
|
init_printer();
|
|
@@ -4054,7 +4268,7 @@ function parseUserAndDomain(str) {
|
|
|
4054
4268
|
const [user, domain] = str.split("@", 2);
|
|
4055
4269
|
if (isValidServerAddress(domain)) {
|
|
4056
4270
|
let processKeyword2 = function(keyword) {
|
|
4057
|
-
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)) {
|
|
4058
4272
|
type = keyword;
|
|
4059
4273
|
} else if (keyword === "force") {
|
|
4060
4274
|
forceFlag = true;
|
|
@@ -4142,7 +4356,7 @@ function parseUsers(positionalArgs, explicitToken) {
|
|
|
4142
4356
|
}
|
|
4143
4357
|
function parseType(finalConfig, values, inferredType) {
|
|
4144
4358
|
const t = inferredType || values.type;
|
|
4145
|
-
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) {
|
|
4146
4360
|
return t;
|
|
4147
4361
|
}
|
|
4148
4362
|
}
|
|
@@ -4240,7 +4454,7 @@ function parseAdditionalForwarding(forwarding) {
|
|
|
4240
4454
|
}
|
|
4241
4455
|
const firstPart = parsed[0];
|
|
4242
4456
|
const [hostPart] = firstPart.split("@");
|
|
4243
|
-
let protocol =
|
|
4457
|
+
let protocol = import_pinggy5.TunnelType.Http;
|
|
4244
4458
|
let remoteDomainRaw;
|
|
4245
4459
|
let remotePort = 0;
|
|
4246
4460
|
if (hostPart.includes("//")) {
|
|
@@ -4273,7 +4487,7 @@ function parseAdditionalForwarding(forwarding) {
|
|
|
4273
4487
|
if (!isValidServerAddress(remoteDomainRaw)) {
|
|
4274
4488
|
return new Error("invalid remote domain");
|
|
4275
4489
|
}
|
|
4276
|
-
protocol =
|
|
4490
|
+
protocol = import_pinggy5.TunnelType.Http;
|
|
4277
4491
|
remotePort = 0;
|
|
4278
4492
|
}
|
|
4279
4493
|
const localDomain = removeIPv6Brackets(parsed[2] || "localhost");
|
|
@@ -4303,7 +4517,7 @@ function parseReverseTunnelAddr(finalConfig, values, primaryType) {
|
|
|
4303
4517
|
if (parsed instanceof Error) return parsed;
|
|
4304
4518
|
forwardingData.push({
|
|
4305
4519
|
address: `${parsed.localDomain}:${parsed.localPort}`,
|
|
4306
|
-
type: primaryType ||
|
|
4520
|
+
type: primaryType || import_pinggy5.TunnelType.Http
|
|
4307
4521
|
});
|
|
4308
4522
|
} else if (slicedForwarding.length === 4) {
|
|
4309
4523
|
const parsed = parseAdditionalForwarding(forwarding);
|
|
@@ -4369,7 +4583,7 @@ function normalizeForwardingToArray(finalConfig, type) {
|
|
|
4369
4583
|
if (typeof fwd !== "string") {
|
|
4370
4584
|
return;
|
|
4371
4585
|
}
|
|
4372
|
-
finalConfig.forwarding = fwd.trim() === "" ? [] : [{ type: type ||
|
|
4586
|
+
finalConfig.forwarding = fwd.trim() === "" ? [] : [{ type: type || import_pinggy5.TunnelType.Http, address: fwd.trim() }];
|
|
4373
4587
|
}
|
|
4374
4588
|
function parseArgs2(finalConfig, remainingPositionals) {
|
|
4375
4589
|
let localserverTls = "";
|
|
@@ -4496,7 +4710,7 @@ function buildFinalConfig(values, positionals, baseConfig) {
|
|
|
4496
4710
|
storeJson(finalConfig, saveconf);
|
|
4497
4711
|
return finalConfig;
|
|
4498
4712
|
}
|
|
4499
|
-
var
|
|
4713
|
+
var import_pinggy5, import_fs6, import_path6, import_net2, domainRegex, KEYWORDS, VALID_PROTOCOLS;
|
|
4500
4714
|
var init_buildConfig = __esm({
|
|
4501
4715
|
"src/cli/buildConfig.ts"() {
|
|
4502
4716
|
"use strict";
|
|
@@ -4505,21 +4719,21 @@ var init_buildConfig = __esm({
|
|
|
4505
4719
|
init_extendedOptions();
|
|
4506
4720
|
init_logger();
|
|
4507
4721
|
init_util();
|
|
4508
|
-
|
|
4722
|
+
import_pinggy5 = require("@pinggy/pinggy");
|
|
4509
4723
|
import_fs6 = __toESM(require("fs"), 1);
|
|
4510
4724
|
import_path6 = __toESM(require("path"), 1);
|
|
4511
4725
|
import_net2 = require("net");
|
|
4512
4726
|
domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
|
|
4513
4727
|
KEYWORDS = /* @__PURE__ */ new Set([
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
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,
|
|
4519
4733
|
"force",
|
|
4520
4734
|
"qr"
|
|
4521
4735
|
]);
|
|
4522
|
-
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];
|
|
4523
4737
|
}
|
|
4524
4738
|
});
|
|
4525
4739
|
|
|
@@ -5103,7 +5317,7 @@ var init_webDebuggerConnection = __esm({
|
|
|
5103
5317
|
});
|
|
5104
5318
|
|
|
5105
5319
|
// src/tui/ink/asciArt.ts
|
|
5106
|
-
var asciiArtPinggyLogo;
|
|
5320
|
+
var asciiArtPinggyLogo, asciiArtPinggyLogoCompact, asciiArtPinggyLogoMedium;
|
|
5107
5321
|
var init_asciArt = __esm({
|
|
5108
5322
|
"src/tui/ink/asciArt.ts"() {
|
|
5109
5323
|
"use strict";
|
|
@@ -5115,6 +5329,13 @@ var init_asciArt = __esm({
|
|
|
5115
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
|
|
5116
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
|
|
5117
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");
|
|
5118
5339
|
}
|
|
5119
5340
|
});
|
|
5120
5341
|
|
|
@@ -5127,6 +5348,54 @@ function colorizeGradient(text) {
|
|
|
5127
5348
|
return `{${color}-fg}${line}{/${color}-fg}`;
|
|
5128
5349
|
}).join("\n");
|
|
5129
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
|
+
}
|
|
5130
5399
|
function createWarningUI(screen) {
|
|
5131
5400
|
return import_blessed.default.box({
|
|
5132
5401
|
parent: screen,
|
|
@@ -5153,21 +5422,26 @@ function createFullUI(screen, urls, greet, tunnelConfig) {
|
|
|
5153
5422
|
height: "100%",
|
|
5154
5423
|
padding: 1
|
|
5155
5424
|
});
|
|
5425
|
+
const { content: logoContent, boxHeight: logoHeight } = selectLogo(
|
|
5426
|
+
screen.width,
|
|
5427
|
+
screen.height
|
|
5428
|
+
);
|
|
5156
5429
|
const logoBox = import_blessed.default.box({
|
|
5157
5430
|
parent: mainContainer,
|
|
5158
5431
|
top: 0,
|
|
5159
5432
|
left: 0,
|
|
5160
5433
|
width: "100%",
|
|
5161
|
-
height:
|
|
5162
|
-
content:
|
|
5434
|
+
height: logoHeight,
|
|
5435
|
+
content: logoContent,
|
|
5163
5436
|
tags: true
|
|
5164
5437
|
});
|
|
5438
|
+
const contentTop = logoHeight + 1;
|
|
5165
5439
|
const contentBox = import_blessed.default.box({
|
|
5166
5440
|
parent: mainContainer,
|
|
5167
|
-
top:
|
|
5441
|
+
top: contentTop,
|
|
5168
5442
|
left: 0,
|
|
5169
5443
|
width: "100%-2",
|
|
5170
|
-
height:
|
|
5444
|
+
height: `100%-${contentTop + 2}`,
|
|
5171
5445
|
padding: 0,
|
|
5172
5446
|
border: {
|
|
5173
5447
|
type: "line"
|
|
@@ -5180,20 +5454,24 @@ function createFullUI(screen, urls, greet, tunnelConfig) {
|
|
|
5180
5454
|
});
|
|
5181
5455
|
let greetHeight = 0;
|
|
5182
5456
|
if (greet) {
|
|
5183
|
-
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({
|
|
5184
5462
|
parent: contentBox,
|
|
5185
5463
|
top: 0,
|
|
5186
5464
|
left: "center",
|
|
5187
|
-
width:
|
|
5188
|
-
height:
|
|
5189
|
-
content: `{bold}${
|
|
5465
|
+
width: greetWidth,
|
|
5466
|
+
height: greetBoxHeight,
|
|
5467
|
+
content: greetLines.map((line) => `{bold}${line}{/bold}`).join("\n"),
|
|
5190
5468
|
tags: true,
|
|
5191
5469
|
align: "center",
|
|
5192
5470
|
style: {
|
|
5193
5471
|
fg: "green"
|
|
5194
5472
|
}
|
|
5195
5473
|
});
|
|
5196
|
-
greetHeight =
|
|
5474
|
+
greetHeight = greetBoxHeight;
|
|
5197
5475
|
}
|
|
5198
5476
|
const upperSectionTop = greetHeight > 0 ? greetHeight : 0;
|
|
5199
5477
|
const upperSection = import_blessed.default.box({
|
|
@@ -5287,21 +5565,22 @@ function createSimpleUI(screen, urls, greet) {
|
|
|
5287
5565
|
});
|
|
5288
5566
|
let currentTop = 0;
|
|
5289
5567
|
if (greet) {
|
|
5568
|
+
const greetWidth = Math.max(20, Math.floor((screen.width - 2) * 0.9));
|
|
5569
|
+
const greetLines = wrapText(greet, greetWidth);
|
|
5290
5570
|
import_blessed.default.box({
|
|
5291
5571
|
parent: mainContainer,
|
|
5292
5572
|
top: currentTop,
|
|
5293
5573
|
left: "center",
|
|
5294
|
-
width:
|
|
5295
|
-
height:
|
|
5296
|
-
content: `{bold}${
|
|
5574
|
+
width: greetWidth,
|
|
5575
|
+
height: greetLines.length,
|
|
5576
|
+
content: greetLines.map((line) => `{bold}${line}{/bold}`).join("\n"),
|
|
5297
5577
|
tags: true,
|
|
5298
5578
|
align: "center",
|
|
5299
5579
|
style: {
|
|
5300
5580
|
fg: "green"
|
|
5301
5581
|
}
|
|
5302
5582
|
});
|
|
5303
|
-
|
|
5304
|
-
currentTop += Math.max(lines, 1) + 1;
|
|
5583
|
+
currentTop += greetLines.length + 1;
|
|
5305
5584
|
}
|
|
5306
5585
|
const urlsBox = import_blessed.default.box({
|
|
5307
5586
|
parent: mainContainer,
|
|
@@ -5340,7 +5619,7 @@ function createSimpleUI(screen, urls, greet) {
|
|
|
5340
5619
|
footerBox
|
|
5341
5620
|
};
|
|
5342
5621
|
}
|
|
5343
|
-
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;
|
|
5344
5623
|
var init_UIComponents = __esm({
|
|
5345
5624
|
"src/tui/blessed/components/UIComponents.ts"() {
|
|
5346
5625
|
"use strict";
|
|
@@ -5349,6 +5628,10 @@ var init_UIComponents = __esm({
|
|
|
5349
5628
|
init_asciArt();
|
|
5350
5629
|
MIN_WIDTH_WARNING = 60;
|
|
5351
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;
|
|
5352
5635
|
}
|
|
5353
5636
|
});
|
|
5354
5637
|
|
|
@@ -7243,6 +7526,8 @@ async function handleDaemonStop() {
|
|
|
7243
7526
|
const result = await stopDaemon();
|
|
7244
7527
|
if (result.ok) {
|
|
7245
7528
|
printer_default.success("Daemon stopped.");
|
|
7529
|
+
} else if (result.reason === DaemonHost.APP) {
|
|
7530
|
+
printer_default.print(import_picocolors6.default.yellow("The Pinggy daemon is running inside the Pinggy desktop app. Please quit the Pinggy app to stop it."));
|
|
7246
7531
|
} else {
|
|
7247
7532
|
printer_default.error(result.error);
|
|
7248
7533
|
}
|
|
@@ -7263,6 +7548,7 @@ function handleDaemonStatus() {
|
|
|
7263
7548
|
printer_default.print(` Port: ${info.port}`);
|
|
7264
7549
|
printer_default.print(` Started: ${info.startedAt}`);
|
|
7265
7550
|
printer_default.print(` Uptime: ${uptimeStr}`);
|
|
7551
|
+
printer_default.print(` Host: ${info.host === DaemonHost.APP ? "Pinggy app (in-process)" : "standalone (CLI)"}`);
|
|
7266
7552
|
}
|
|
7267
7553
|
var import_picocolors6;
|
|
7268
7554
|
var init_daemonCommandsHandler = __esm({
|
|
@@ -7273,6 +7559,7 @@ var init_daemonCommandsHandler = __esm({
|
|
|
7273
7559
|
import_picocolors6 = __toESM(require("picocolors"), 1);
|
|
7274
7560
|
init_configStore();
|
|
7275
7561
|
init_daemonManager();
|
|
7562
|
+
init_ipcRoutes();
|
|
7276
7563
|
init_serviceInstaller();
|
|
7277
7564
|
init_helpMessages();
|
|
7278
7565
|
init_util();
|
|
@@ -8007,6 +8294,7 @@ var init_subcommands = __esm({
|
|
|
8007
8294
|
// src/main.ts
|
|
8008
8295
|
var main_exports = {};
|
|
8009
8296
|
__export(main_exports, {
|
|
8297
|
+
DaemonHost: () => DaemonHost,
|
|
8010
8298
|
RemoteManagementUnauthorizedError: () => RemoteManagementUnauthorizedError,
|
|
8011
8299
|
TunnelManager: () => TunnelManager,
|
|
8012
8300
|
TunnelOperations: () => TunnelOperations,
|
|
@@ -8069,6 +8357,7 @@ var init_main = __esm({
|
|
|
8069
8357
|
init_buildAndStartTunnel();
|
|
8070
8358
|
init_subcommands();
|
|
8071
8359
|
init_daemonChild();
|
|
8360
|
+
init_ipcRoutes();
|
|
8072
8361
|
init_daemonManager();
|
|
8073
8362
|
currentFile = (0, import_url2.fileURLToPath)(importMetaUrl);
|
|
8074
8363
|
entryFile = null;
|
|
@@ -8308,7 +8597,7 @@ var init_ipcServer = __esm({
|
|
|
8308
8597
|
},
|
|
8309
8598
|
[Route.RestartTunnel]: async (req) => {
|
|
8310
8599
|
if (!req.tunnelid) throw new Error("Missing 'tunnelid' field");
|
|
8311
|
-
return await this.ops.handleRestart(req.tunnelid);
|
|
8600
|
+
return await this.ops.handleRestart(req.tunnelid, req.noWait);
|
|
8312
8601
|
},
|
|
8313
8602
|
[Route.UpdateConfig]: async (req) => {
|
|
8314
8603
|
if (!req.config) throw new Error("Missing 'config' field");
|
|
@@ -8375,6 +8664,10 @@ var init_ipcServer = __esm({
|
|
|
8375
8664
|
},
|
|
8376
8665
|
[Route.Shutdown]: () => {
|
|
8377
8666
|
logger.info("Daemon shutdown requested via IPC");
|
|
8667
|
+
if (getDaemonHost() === DaemonHost.APP) {
|
|
8668
|
+
logger.info("Shutdown refused: daemon is hosted in-process by the Pinggy app");
|
|
8669
|
+
return { status: ShutdownStatus.RefusedInApp, errors: [] };
|
|
8670
|
+
}
|
|
8378
8671
|
const errors = [];
|
|
8379
8672
|
const step = (label, fn) => {
|
|
8380
8673
|
try {
|
|
@@ -8389,7 +8682,7 @@ var init_ipcServer = __esm({
|
|
|
8389
8682
|
step("clearDaemonState", clearDaemonState);
|
|
8390
8683
|
step("stopAllTunnels", () => TunnelManager.getInstance().stopAllTunnels());
|
|
8391
8684
|
setTimeout(() => process.exit(0), 200);
|
|
8392
|
-
return { status:
|
|
8685
|
+
return { status: ShutdownStatus.ShuttingDown, errors };
|
|
8393
8686
|
}
|
|
8394
8687
|
};
|
|
8395
8688
|
}
|
|
@@ -8847,6 +9140,8 @@ var init_sessionTracker = __esm({
|
|
|
8847
9140
|
// src/daemon/lifecycle/daemonChild.ts
|
|
8848
9141
|
var daemonChild_exports = {};
|
|
8849
9142
|
__export(daemonChild_exports, {
|
|
9143
|
+
DaemonHost: () => DaemonHost,
|
|
9144
|
+
getDaemonHost: () => getDaemonHost,
|
|
8850
9145
|
removeDaemonInfo: () => removeDaemonInfo,
|
|
8851
9146
|
runDaemonChild: () => runDaemonChild,
|
|
8852
9147
|
setDaemonSessionTracker: () => setDaemonSessionTracker,
|
|
@@ -8854,6 +9149,9 @@ __export(daemonChild_exports, {
|
|
|
8854
9149
|
trackTunnelStart: () => trackTunnelStart,
|
|
8855
9150
|
trackTunnelStop: () => trackTunnelStop
|
|
8856
9151
|
});
|
|
9152
|
+
function getDaemonHost() {
|
|
9153
|
+
return daemonHost;
|
|
9154
|
+
}
|
|
8857
9155
|
function setDaemonSessionTracker(st) {
|
|
8858
9156
|
sessionTrackerRef = st;
|
|
8859
9157
|
}
|
|
@@ -8982,6 +9280,7 @@ async function restoreCrashedTunnels(manager) {
|
|
|
8982
9280
|
async function runDaemonChild(opts = {}) {
|
|
8983
9281
|
const installSignalHandlers = opts.installSignalHandlers ?? true;
|
|
8984
9282
|
const exitOnFailure = opts.exitOnFailure ?? true;
|
|
9283
|
+
daemonHost = opts.host ?? DaemonHost.CLI;
|
|
8985
9284
|
ensurePinggyConfigDir();
|
|
8986
9285
|
const persistedLevel = readDaemonConfig()?.logLevel;
|
|
8987
9286
|
const envLevel = process.env.PINGGY_LOG_LEVEL;
|
|
@@ -9057,7 +9356,8 @@ async function runDaemonChild(opts = {}) {
|
|
|
9057
9356
|
const info = {
|
|
9058
9357
|
pid: process.pid,
|
|
9059
9358
|
port,
|
|
9060
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9359
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9360
|
+
host: daemonHost
|
|
9061
9361
|
};
|
|
9062
9362
|
writeDaemonInfo(info);
|
|
9063
9363
|
logger.info("Daemon info written", info);
|
|
@@ -9090,7 +9390,7 @@ async function runDaemonChild(opts = {}) {
|
|
|
9090
9390
|
throw err;
|
|
9091
9391
|
}
|
|
9092
9392
|
}
|
|
9093
|
-
var import_node_fs7, daemonState, sessionTrackerRef;
|
|
9393
|
+
var import_node_fs7, daemonState, daemonHost, sessionTrackerRef;
|
|
9094
9394
|
var init_daemonChild = __esm({
|
|
9095
9395
|
"src/daemon/lifecycle/daemonChild.ts"() {
|
|
9096
9396
|
"use strict";
|
|
@@ -9108,6 +9408,7 @@ var init_daemonChild = __esm({
|
|
|
9108
9408
|
init_util();
|
|
9109
9409
|
init_ipcRoutes();
|
|
9110
9410
|
daemonState = { tunnels: [], lastUpdated: "" };
|
|
9411
|
+
daemonHost = DaemonHost.CLI;
|
|
9111
9412
|
sessionTrackerRef = null;
|
|
9112
9413
|
}
|
|
9113
9414
|
});
|
|
@@ -9194,13 +9495,15 @@ async function ensureDaemonRunning() {
|
|
|
9194
9495
|
const { runDaemonChild: runDaemonChild2 } = await Promise.resolve().then(() => (init_daemonChild(), daemonChild_exports));
|
|
9195
9496
|
const handle = await runDaemonChild2({
|
|
9196
9497
|
installSignalHandlers: false,
|
|
9197
|
-
exitOnFailure: false
|
|
9498
|
+
exitOnFailure: false,
|
|
9499
|
+
host: DaemonHost.APP
|
|
9198
9500
|
});
|
|
9199
9501
|
inProcessHandle = handle;
|
|
9200
9502
|
return getDaemonInfo() ?? {
|
|
9201
9503
|
pid: handle.pid,
|
|
9202
9504
|
port: handle.port,
|
|
9203
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9505
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9506
|
+
host: DaemonHost.APP
|
|
9204
9507
|
};
|
|
9205
9508
|
}
|
|
9206
9509
|
return startDaemon();
|
|
@@ -9262,6 +9565,13 @@ async function stopDaemon() {
|
|
|
9262
9565
|
const client = new IPCClient2(info.port);
|
|
9263
9566
|
const result = await client.shutdown();
|
|
9264
9567
|
logger.debug("Sent shutdown command to daemon", { result });
|
|
9568
|
+
if (result?.status === ShutdownStatus.RefusedInApp) {
|
|
9569
|
+
return {
|
|
9570
|
+
ok: false,
|
|
9571
|
+
reason: DaemonHost.APP,
|
|
9572
|
+
error: "The daemon is running inside the Pinggy app."
|
|
9573
|
+
};
|
|
9574
|
+
}
|
|
9265
9575
|
if (Array.isArray(result?.errors)) daemonErrors = result.errors;
|
|
9266
9576
|
} catch (e) {
|
|
9267
9577
|
return { ok: false, error: `Failed to reach daemon: ${errorMessage(e)}` };
|
|
@@ -9293,6 +9603,7 @@ var init_daemonManager = __esm({
|
|
|
9293
9603
|
import_node_fs8 = __toESM(require("fs"), 1);
|
|
9294
9604
|
import_node_child_process2 = require("child_process");
|
|
9295
9605
|
init_configDir();
|
|
9606
|
+
init_ipcRoutes();
|
|
9296
9607
|
init_logger();
|
|
9297
9608
|
init_types();
|
|
9298
9609
|
init_util();
|
|
@@ -9583,6 +9894,13 @@ var init_daemonHealth = __esm({
|
|
|
9583
9894
|
bindPid(pid) {
|
|
9584
9895
|
this.originalPid = pid;
|
|
9585
9896
|
}
|
|
9897
|
+
/**
|
|
9898
|
+
* Forget a previous loss verdict. Called when the client re-binds to a
|
|
9899
|
+
* live daemon via ensureDaemon():
|
|
9900
|
+
*/
|
|
9901
|
+
reset() {
|
|
9902
|
+
this.lost = false;
|
|
9903
|
+
}
|
|
9586
9904
|
isLost() {
|
|
9587
9905
|
return this.lost;
|
|
9588
9906
|
}
|
|
@@ -9709,6 +10027,7 @@ var init_tunnelClient = __esm({
|
|
|
9709
10027
|
const info = await ensureDaemonRunning();
|
|
9710
10028
|
this.ipc = new IPCClient(info.port, this.origin);
|
|
9711
10029
|
this.health.bindPid(info.pid);
|
|
10030
|
+
this.health.reset();
|
|
9712
10031
|
}
|
|
9713
10032
|
static async forRemoteManagement() {
|
|
9714
10033
|
const client = new _TunnelClient({ origin: "remote" });
|
|
@@ -9773,15 +10092,30 @@ var init_tunnelClient = __esm({
|
|
|
9773
10092
|
this.assertClient();
|
|
9774
10093
|
return this.ipc.getTunnel(tunnelId);
|
|
9775
10094
|
}
|
|
9776
|
-
async handleRestart(tunnelId) {
|
|
10095
|
+
async handleRestart(tunnelId, noWait) {
|
|
9777
10096
|
this.assertClient();
|
|
9778
|
-
return this.ipc.restartTunnel(tunnelId);
|
|
10097
|
+
return this.ipc.restartTunnel(tunnelId, noWait);
|
|
9779
10098
|
}
|
|
9780
10099
|
async shutdown() {
|
|
9781
10100
|
this.assertClient();
|
|
9782
10101
|
await this.ipc.shutdown();
|
|
9783
10102
|
this.close();
|
|
9784
10103
|
}
|
|
10104
|
+
/**
|
|
10105
|
+
* Tear down the daemon hosted in THIS process, if any. Stops all tunnels
|
|
10106
|
+
* and removes daemon.json + daemon-state.json so the next CLI run doesn't
|
|
10107
|
+
* "crash-recover" tunnels that died with the host application.
|
|
10108
|
+
*/
|
|
10109
|
+
shutdownInProcessDaemon() {
|
|
10110
|
+
const handle = getInProcessDaemonHandle();
|
|
10111
|
+
if (!handle) return false;
|
|
10112
|
+
try {
|
|
10113
|
+
this.close();
|
|
10114
|
+
} catch {
|
|
10115
|
+
}
|
|
10116
|
+
handle.shutdown();
|
|
10117
|
+
return true;
|
|
10118
|
+
}
|
|
9785
10119
|
async getLogLevel() {
|
|
9786
10120
|
this.assertClient();
|
|
9787
10121
|
const res = await this.ipc.getLogLevel();
|
|
@@ -9914,11 +10248,13 @@ __export(index_exports, {
|
|
|
9914
10248
|
findConfig: () => findConfig,
|
|
9915
10249
|
findConfigByName: () => findConfigByName,
|
|
9916
10250
|
getAutoStartConfigs: () => getAutoStartConfigs,
|
|
10251
|
+
getDaemonInfo: () => getDaemonInfo,
|
|
9917
10252
|
getDaemonInfoPath: () => getDaemonInfoPath,
|
|
9918
10253
|
getPinggyConfigDir: () => getPinggyConfigDir,
|
|
9919
10254
|
getRemoteManagementState: () => getRemoteManagementState,
|
|
9920
10255
|
getTunnelConfigDir: () => getTunnelConfigDir,
|
|
9921
10256
|
initiateRemoteManagement: () => initiateRemoteManagement,
|
|
10257
|
+
isDaemonRunning: () => isDaemonRunning,
|
|
9922
10258
|
listSavedConfigs: () => listSavedConfigs,
|
|
9923
10259
|
loadConfigByName: () => loadConfigByName,
|
|
9924
10260
|
sanitizeName: () => sanitizeName,
|
|
@@ -10019,6 +10355,7 @@ init_printer();
|
|
|
10019
10355
|
init_TunnelManager();
|
|
10020
10356
|
init_handler();
|
|
10021
10357
|
init_tunnelClient();
|
|
10358
|
+
init_daemonManager();
|
|
10022
10359
|
init_logger();
|
|
10023
10360
|
init_remoteManagement();
|
|
10024
10361
|
init_types();
|
|
@@ -10061,11 +10398,13 @@ verifyAndLoad().catch((err) => {
|
|
|
10061
10398
|
findConfig,
|
|
10062
10399
|
findConfigByName,
|
|
10063
10400
|
getAutoStartConfigs,
|
|
10401
|
+
getDaemonInfo,
|
|
10064
10402
|
getDaemonInfoPath,
|
|
10065
10403
|
getPinggyConfigDir,
|
|
10066
10404
|
getRemoteManagementState,
|
|
10067
10405
|
getTunnelConfigDir,
|
|
10068
10406
|
initiateRemoteManagement,
|
|
10407
|
+
isDaemonRunning,
|
|
10069
10408
|
listSavedConfigs,
|
|
10070
10409
|
loadConfigByName,
|
|
10071
10410
|
sanitizeName,
|