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
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DaemonHost,
|
|
2
3
|
IPCClient,
|
|
3
|
-
SessionMode
|
|
4
|
-
|
|
4
|
+
SessionMode,
|
|
5
|
+
ShutdownStatus
|
|
6
|
+
} from "./chunk-YTJEV27N.js";
|
|
5
7
|
import {
|
|
6
8
|
TunnelAlreadyRunningError,
|
|
7
9
|
TunnelManager,
|
|
@@ -10,10 +12,10 @@ import {
|
|
|
10
12
|
getVersion,
|
|
11
13
|
isValidPort,
|
|
12
14
|
printer_default
|
|
13
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-PQSLSIDA.js";
|
|
14
16
|
import {
|
|
15
17
|
logger
|
|
16
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-P7WNBN7F.js";
|
|
17
19
|
import {
|
|
18
20
|
getDaemonInfoPath,
|
|
19
21
|
getDaemonLogPath
|
|
@@ -127,7 +129,7 @@ var RemoteManagementStatus = {
|
|
|
127
129
|
};
|
|
128
130
|
|
|
129
131
|
// src/remote_management/remote_schema.ts
|
|
130
|
-
import { TunnelType } from "@pinggy/pinggy";
|
|
132
|
+
import { HaProxyVersion, TunnelType } from "@pinggy/pinggy";
|
|
131
133
|
import { z } from "zod";
|
|
132
134
|
var HeaderModificationSchema = z.object({
|
|
133
135
|
key: z.string(),
|
|
@@ -207,7 +209,7 @@ var UpdateConfigSchema = z.object({
|
|
|
207
209
|
var ForwardingEntryV2Schema = z.object({
|
|
208
210
|
listenAddress: z.string().optional(),
|
|
209
211
|
address: z.string(),
|
|
210
|
-
type: z.enum([TunnelType.Http, TunnelType.Tcp, TunnelType.Udp, TunnelType.Tls, TunnelType.TlsTcp])
|
|
212
|
+
type: z.enum([TunnelType.Http, TunnelType.Tcp, TunnelType.Udp, TunnelType.Tls, TunnelType.TlsTcp]).default(TunnelType.Http)
|
|
211
213
|
});
|
|
212
214
|
var OptionalSchema = z.object({
|
|
213
215
|
sniServerName: z.string().optional(),
|
|
@@ -251,7 +253,9 @@ var TunnelConfigV1Schema = z.object({
|
|
|
251
253
|
httpsOnly: z.boolean().optional(),
|
|
252
254
|
originalRequestUrl: z.boolean().optional(),
|
|
253
255
|
allowPreflight: z.boolean().optional(),
|
|
254
|
-
|
|
256
|
+
haProxy: z.enum([HaProxyVersion.V1, HaProxyVersion.V2]).optional(),
|
|
257
|
+
serve: z.string().optional(),
|
|
258
|
+
optional: z.record(z.string(), z.unknown()).optional()
|
|
255
259
|
});
|
|
256
260
|
var TUNNEL_CONFIG_V1_KEYS = Object.keys(
|
|
257
261
|
TunnelConfigV1Schema.shape
|
|
@@ -284,6 +288,7 @@ function pinggyOptionsToTunnelConfigV1(opts, configStoredInCli) {
|
|
|
284
288
|
httpsOnly: opts.httpsOnly ?? false,
|
|
285
289
|
originalRequestUrl: opts.originalRequestUrl ?? false,
|
|
286
290
|
allowPreflight: opts.allowPreflight ?? false,
|
|
291
|
+
haProxy: opts.haProxy,
|
|
287
292
|
optional: opts.optional || {}
|
|
288
293
|
};
|
|
289
294
|
}
|
|
@@ -504,11 +509,11 @@ var TunnelOperations = class {
|
|
|
504
509
|
startPromise.catch((err) => {
|
|
505
510
|
logger.error("No-wait startTunnel failed", { tunnelid, err: String(err) });
|
|
506
511
|
});
|
|
507
|
-
return this.buildPendingTunnelResponseV2(tunnelid, tunnelConfig, config, config.configId, config.name, config.
|
|
512
|
+
return this.buildPendingTunnelResponseV2(tunnelid, tunnelConfig, config, config.configId, config.name, config.serve);
|
|
508
513
|
}
|
|
509
514
|
await startPromise;
|
|
510
515
|
const tunnelPconfig = await this.tunnelManager.getTunnelConfig("", tunnelid);
|
|
511
|
-
return this.buildTunnelResponseV2(tunnelid, tunnelPconfig, config, config.configId, config.name, config.
|
|
516
|
+
return this.buildTunnelResponseV2(tunnelid, tunnelPconfig, config, config.configId, config.name, config.serve);
|
|
512
517
|
} catch (err) {
|
|
513
518
|
if (err instanceof TunnelAlreadyRunningError) {
|
|
514
519
|
return this.error(ErrorCode.TunnelAlreadyRunningError, err, err.message);
|
|
@@ -530,10 +535,12 @@ var TunnelOperations = class {
|
|
|
530
535
|
if (noWait) {
|
|
531
536
|
const existing = this.tunnelManager.getManagedTunnel(config.configid);
|
|
532
537
|
if (!existing.tunnelConfig) throw new Error("Invalid tunnel state before configuration update");
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
538
|
+
if (!existing.isStopped) {
|
|
539
|
+
this.tunnelManager.updateConfig(updateOpts).catch((err) => {
|
|
540
|
+
logger.error("No-wait updateConfig failed", { configid: config.configid, err: String(err) });
|
|
541
|
+
});
|
|
542
|
+
return this.buildPendingTunnelResponse(existing.tunnelid, existing.tunnelConfig, config.configid, existing.tunnelName, existing.serve);
|
|
543
|
+
}
|
|
537
544
|
}
|
|
538
545
|
const tunnel = await this.tunnelManager.updateConfig(updateOpts);
|
|
539
546
|
if (!tunnel.instance || !tunnel.tunnelConfig)
|
|
@@ -548,10 +555,12 @@ var TunnelOperations = class {
|
|
|
548
555
|
if (noWait) {
|
|
549
556
|
const existing = this.tunnelManager.getManagedTunnel(config.configId);
|
|
550
557
|
if (!existing.tunnelConfig) throw new Error("Invalid tunnel state before configuration update");
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
558
|
+
if (!existing.isStopped) {
|
|
559
|
+
this.tunnelManager.updateConfig(config).catch((err) => {
|
|
560
|
+
logger.error("No-wait updateConfigV2 failed", { configId: config.configId, err: String(err) });
|
|
561
|
+
});
|
|
562
|
+
return this.buildPendingTunnelResponseV2(existing.tunnelid, existing.tunnelConfig, config, config.configId, existing.tunnelName, existing.serve);
|
|
563
|
+
}
|
|
555
564
|
}
|
|
556
565
|
const tunnel = await this.tunnelManager.updateConfig(config);
|
|
557
566
|
if (!tunnel.instance || !tunnel.tunnelConfig)
|
|
@@ -698,13 +707,71 @@ import WebSocket from "ws";
|
|
|
698
707
|
|
|
699
708
|
// src/remote_management/websocket_printer.ts
|
|
700
709
|
import pico from "picocolors";
|
|
701
|
-
var
|
|
702
|
-
var
|
|
710
|
+
var PENDING_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
711
|
+
var PendingTracker = class {
|
|
703
712
|
constructor() {
|
|
704
|
-
this.
|
|
713
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
714
|
+
}
|
|
715
|
+
queue(entry) {
|
|
716
|
+
this.latestConfigId = entry.configId;
|
|
717
|
+
this.entries.set(entry.configId, entry);
|
|
718
|
+
}
|
|
719
|
+
/** Store an entry without claiming the latest cursor. */
|
|
720
|
+
upsert(entry) {
|
|
721
|
+
this.entries.set(entry.configId, entry);
|
|
722
|
+
}
|
|
723
|
+
get(configId) {
|
|
724
|
+
return this.entries.get(configId);
|
|
725
|
+
}
|
|
726
|
+
find(configId, tunnelId) {
|
|
727
|
+
const byConfigId = this.entries.get(configId);
|
|
728
|
+
if (byConfigId) {
|
|
729
|
+
return byConfigId;
|
|
730
|
+
}
|
|
731
|
+
if (!tunnelId) {
|
|
732
|
+
return void 0;
|
|
733
|
+
}
|
|
734
|
+
for (const entry of this.entries.values()) {
|
|
735
|
+
if (entry.tunnelId === tunnelId) {
|
|
736
|
+
return entry;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return void 0;
|
|
740
|
+
}
|
|
741
|
+
delete(configId) {
|
|
742
|
+
this.entries.delete(configId);
|
|
743
|
+
if (this.latestConfigId === configId) {
|
|
744
|
+
this.latestConfigId = void 0;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
get latest() {
|
|
748
|
+
return this.latestConfigId;
|
|
705
749
|
}
|
|
706
|
-
|
|
707
|
-
this.
|
|
750
|
+
get hasLatest() {
|
|
751
|
+
return this.entries.size > 0 && this.latestConfigId !== void 0;
|
|
752
|
+
}
|
|
753
|
+
isLatest(configId) {
|
|
754
|
+
return configId === this.latestConfigId;
|
|
755
|
+
}
|
|
756
|
+
cleanupExpired(onTimeout) {
|
|
757
|
+
const now = Date.now();
|
|
758
|
+
for (const [configId, entry] of this.entries) {
|
|
759
|
+
if (now - entry.queuedAt <= PENDING_TIMEOUT_MS) {
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
const wasLatest = this.isLatest(configId);
|
|
763
|
+
this.delete(configId);
|
|
764
|
+
onTimeout(entry, wasLatest);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
var RemoteManagementWebSocketPrinter = class {
|
|
769
|
+
constructor() {
|
|
770
|
+
this.pendingStarts = new PendingTracker();
|
|
771
|
+
this.pendingActions = new PendingTracker();
|
|
772
|
+
// tunnelId -> details, fed by monitorList. Stop/restart requests only carry
|
|
773
|
+
// a tunnelId; without this the spinner shows the raw id.
|
|
774
|
+
this.tunnelDetailsCache = /* @__PURE__ */ new Map();
|
|
708
775
|
}
|
|
709
776
|
queueStart(config) {
|
|
710
777
|
this.cleanupExpiredPendingStarts();
|
|
@@ -713,24 +780,23 @@ var RemoteManagementWebSocketPrinter = class {
|
|
|
713
780
|
configName: this.getConfigNameFromRequest(config),
|
|
714
781
|
queuedAt: Date.now()
|
|
715
782
|
};
|
|
716
|
-
this.
|
|
717
|
-
this.pendingStarts.set(entry.configId, entry);
|
|
783
|
+
this.pendingStarts.queue(entry);
|
|
718
784
|
printer_default.startSpinner("Starting tunnel with config name: " + entry.configName);
|
|
719
785
|
}
|
|
720
786
|
failQueuedStart(config, reason) {
|
|
721
787
|
const configId = this.getConfigIdFromRequest(config);
|
|
722
788
|
const pending = this.pendingStarts.get(configId);
|
|
723
789
|
const configName = pending?.configName || this.getConfigNameFromRequest(config);
|
|
790
|
+
const wasLatest = this.pendingStarts.isLatest(configId);
|
|
724
791
|
this.pendingStarts.delete(configId);
|
|
725
|
-
if (
|
|
726
|
-
this.latestPendingConfigId = void 0;
|
|
792
|
+
if (wasLatest) {
|
|
727
793
|
printer_default.stopSpinnerFail(`Failed to start tunnel with config name: ${configName}. ${reason}`);
|
|
728
794
|
}
|
|
729
795
|
}
|
|
730
796
|
handleStartResult(config, result) {
|
|
731
797
|
this.cleanupExpiredPendingStarts();
|
|
732
798
|
const requestedConfigId = this.getConfigIdFromRequest(config);
|
|
733
|
-
if (this.
|
|
799
|
+
if (this.pendingStarts.latest && !this.pendingStarts.isLatest(requestedConfigId)) {
|
|
734
800
|
this.pendingStarts.delete(requestedConfigId);
|
|
735
801
|
return;
|
|
736
802
|
}
|
|
@@ -738,20 +804,20 @@ var RemoteManagementWebSocketPrinter = class {
|
|
|
738
804
|
this.failQueuedStart(config, result.message);
|
|
739
805
|
return;
|
|
740
806
|
}
|
|
741
|
-
const configId = this.getConfigIdFromTunnel(result);
|
|
742
807
|
const pending = this.pendingStarts.get(requestedConfigId) || {
|
|
743
808
|
configId: requestedConfigId,
|
|
744
809
|
configName: this.getConfigNameFromRequest(config),
|
|
745
810
|
queuedAt: Date.now()
|
|
746
811
|
};
|
|
747
812
|
pending.tunnelId = result.tunnelid;
|
|
748
|
-
this.pendingStarts.
|
|
813
|
+
this.pendingStarts.upsert(pending);
|
|
749
814
|
if (result.remoteurls.length > 0) {
|
|
750
815
|
this.completePendingStart(pending, result.remoteurls);
|
|
751
816
|
}
|
|
752
817
|
}
|
|
753
818
|
printStopRequested(tunnelId) {
|
|
754
819
|
const details = this.resolveTunnelDetails(tunnelId);
|
|
820
|
+
this.clearPendingAction(details.configId, tunnelId);
|
|
755
821
|
printer_default.startSpinner("Stopping tunnel with config name: " + details.configName);
|
|
756
822
|
}
|
|
757
823
|
handleStopResult(tunnelId, result) {
|
|
@@ -765,6 +831,7 @@ var RemoteManagementWebSocketPrinter = class {
|
|
|
765
831
|
}
|
|
766
832
|
printRestartRequested(tunnelId) {
|
|
767
833
|
const details = this.resolveTunnelDetails(tunnelId);
|
|
834
|
+
this.clearPendingAction(details.configId, tunnelId);
|
|
768
835
|
printer_default.startSpinner("Restarting tunnel with config name: " + details.configName);
|
|
769
836
|
}
|
|
770
837
|
handleRestartResult(tunnelId, result) {
|
|
@@ -774,48 +841,101 @@ var RemoteManagementWebSocketPrinter = class {
|
|
|
774
841
|
printer_default.stopSpinnerFail("Failed to restart tunnel with config name: " + details.configName);
|
|
775
842
|
return;
|
|
776
843
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
844
|
+
this.queuePendingAction("restart", details.configId, details.configName, result.tunnelid);
|
|
845
|
+
}
|
|
846
|
+
printUpdateConfigRequested(config) {
|
|
847
|
+
const configName = this.getConfigNameFromRequest(config);
|
|
848
|
+
this.clearPendingAction(this.getConfigIdFromRequest(config));
|
|
849
|
+
printer_default.startSpinner("Updating tunnel configuration for: " + configName);
|
|
850
|
+
}
|
|
851
|
+
handleUpdateConfigResult(config, result) {
|
|
852
|
+
const configId = this.getConfigIdFromRequest(config);
|
|
853
|
+
const configName = this.getConfigNameFromRequest(config);
|
|
854
|
+
if (isErrorResponse(result)) {
|
|
855
|
+
printer_default.stopSpinnerFail(`Failed to update tunnel configuration for: ${configName}. ${result.message}`);
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
if (result.status.state === "exited" /* Exited */) {
|
|
859
|
+
printer_default.stopSpinnerSuccess(`Tunnel configuration updated: ${configName}.`);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
this.queuePendingAction("update", configId, configName, result.tunnelid);
|
|
863
|
+
}
|
|
864
|
+
clearPendingAction(configId, tunnelId) {
|
|
865
|
+
const entry = this.pendingActions.find(configId, tunnelId);
|
|
866
|
+
if (!entry) {
|
|
867
|
+
return;
|
|
783
868
|
}
|
|
869
|
+
this.pendingActions.delete(entry.configId);
|
|
870
|
+
}
|
|
871
|
+
queuePendingAction(action, configId, configName, tunnelId) {
|
|
872
|
+
this.cleanupExpiredPendingActions();
|
|
873
|
+
this.pendingActions.queue({ action, configId, configName, queuedAt: Date.now(), tunnelId });
|
|
874
|
+
}
|
|
875
|
+
printActionComplete(action, configName, urls) {
|
|
876
|
+
const message = action === "restart" ? `Restarted tunnel with config name: ${configName}` : `Tunnel configuration updated and restarted: ${configName}`;
|
|
877
|
+
printer_default.stopSpinnerSuccess(message);
|
|
878
|
+
printer_default.info(pico.cyanBright("Remote URLs:"));
|
|
879
|
+
urls.forEach((url) => printer_default.print(" " + pico.magentaBright(url)));
|
|
784
880
|
}
|
|
785
881
|
monitorList(result) {
|
|
786
882
|
this.cleanupExpiredPendingStarts();
|
|
787
|
-
|
|
883
|
+
this.cleanupExpiredPendingActions();
|
|
884
|
+
if (!Array.isArray(result)) {
|
|
788
885
|
return;
|
|
789
886
|
}
|
|
790
887
|
for (const tunnel of result) {
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
888
|
+
this.tunnelDetailsCache.set(tunnel.tunnelid, {
|
|
889
|
+
configId: this.getConfigIdFromTunnel(tunnel),
|
|
890
|
+
configName: this.getConfigNameFromTunnel(tunnel)
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
this.resolvePendingFromList(
|
|
894
|
+
result,
|
|
895
|
+
this.pendingStarts,
|
|
896
|
+
(entry, urls) => this.completePendingStart(entry, urls),
|
|
897
|
+
(entry, reason) => printer_default.stopSpinnerFail(`Tunnel start did not complete for config name: ${entry.configName}. ${reason}`)
|
|
898
|
+
);
|
|
899
|
+
this.resolvePendingFromList(
|
|
900
|
+
result,
|
|
901
|
+
this.pendingActions,
|
|
902
|
+
(entry, urls) => {
|
|
903
|
+
this.pendingActions.delete(entry.configId);
|
|
904
|
+
this.printActionComplete(entry.action, entry.configName, urls);
|
|
905
|
+
},
|
|
906
|
+
(entry, reason) => {
|
|
907
|
+
const verb = entry.action === "restart" ? "Restart" : "Configuration update";
|
|
908
|
+
printer_default.stopSpinnerFail(`${verb} did not complete for config name: ${entry.configName}. ${reason}`);
|
|
794
909
|
}
|
|
795
|
-
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
resolvePendingFromList(tunnels, tracker, onComplete, onExited) {
|
|
913
|
+
if (!tracker.hasLatest) {
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
for (const tunnel of tunnels) {
|
|
917
|
+
const entry = tracker.find(this.getConfigIdFromTunnel(tunnel), tunnel.tunnelid);
|
|
918
|
+
if (!entry || !tracker.isLatest(entry.configId)) {
|
|
796
919
|
continue;
|
|
797
920
|
}
|
|
798
|
-
|
|
799
|
-
|
|
921
|
+
entry.tunnelId = tunnel.tunnelid;
|
|
922
|
+
tracker.upsert(entry);
|
|
800
923
|
if (tunnel.remoteurls.length > 0) {
|
|
801
|
-
|
|
924
|
+
onComplete(entry, tunnel.remoteurls);
|
|
802
925
|
continue;
|
|
803
926
|
}
|
|
804
927
|
if (tunnel.status.state === "exited" /* Exited */) {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
this.latestPendingConfigId = void 0;
|
|
808
|
-
printer_default.stopSpinnerFail(`Tunnel start did not complete for config name: ${pending.configName}. ${reason}`);
|
|
928
|
+
tracker.delete(entry.configId);
|
|
929
|
+
onExited(entry, tunnel.status.errormsg || "Tunnel exited before a public URL was assigned");
|
|
809
930
|
}
|
|
810
931
|
}
|
|
811
932
|
}
|
|
812
933
|
completePendingStart(entry, urls) {
|
|
813
|
-
if (this.
|
|
934
|
+
if (this.pendingStarts.latest && !this.pendingStarts.isLatest(entry.configId)) {
|
|
814
935
|
this.pendingStarts.delete(entry.configId);
|
|
815
936
|
return;
|
|
816
937
|
}
|
|
817
938
|
this.pendingStarts.delete(entry.configId);
|
|
818
|
-
this.latestPendingConfigId = void 0;
|
|
819
939
|
printer_default.stopSpinnerSuccess(`Tunnel started with config name: ${entry.configName}.`);
|
|
820
940
|
printer_default.info(pico.cyanBright("Remote URLs:"));
|
|
821
941
|
(urls ?? []).forEach(
|
|
@@ -823,28 +943,27 @@ var RemoteManagementWebSocketPrinter = class {
|
|
|
823
943
|
);
|
|
824
944
|
}
|
|
825
945
|
cleanupExpiredPendingStarts() {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
if (
|
|
829
|
-
|
|
946
|
+
this.pendingStarts.cleanupExpired((entry, wasLatest) => {
|
|
947
|
+
const message = `Timed out while waiting for tunnel URL for config name: ${entry.configName}`;
|
|
948
|
+
if (wasLatest) {
|
|
949
|
+
printer_default.stopSpinnerFail(message);
|
|
950
|
+
} else {
|
|
951
|
+
printer_default.warn(message);
|
|
830
952
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
logger.warn("Pending websocket start entry expired", { configId, tunnelId: entry.tunnelId });
|
|
834
|
-
}
|
|
953
|
+
logger.warn("Pending websocket start entry expired", { configId: entry.configId, tunnelId: entry.tunnelId });
|
|
954
|
+
});
|
|
835
955
|
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
return entry;
|
|
956
|
+
cleanupExpiredPendingActions() {
|
|
957
|
+
this.pendingActions.cleanupExpired((entry, wasLatest) => {
|
|
958
|
+
const verb = entry.action === "restart" ? "restart" : "configuration update";
|
|
959
|
+
const message = `Timed out while waiting for tunnel URL after ${verb} for config name: ${entry.configName}`;
|
|
960
|
+
if (wasLatest) {
|
|
961
|
+
printer_default.stopSpinnerFail(message);
|
|
962
|
+
} else {
|
|
963
|
+
printer_default.warn(message);
|
|
845
964
|
}
|
|
846
|
-
|
|
847
|
-
|
|
965
|
+
logger.warn("Pending websocket action entry expired", { configId: entry.configId, tunnelId: entry.tunnelId, action: entry.action });
|
|
966
|
+
});
|
|
848
967
|
}
|
|
849
968
|
resolveTunnelDetails(tunnelId, result) {
|
|
850
969
|
if (result && !isErrorResponse(result)) {
|
|
@@ -853,7 +972,7 @@ var RemoteManagementWebSocketPrinter = class {
|
|
|
853
972
|
configName: this.getConfigNameFromTunnel(result)
|
|
854
973
|
};
|
|
855
974
|
}
|
|
856
|
-
return {
|
|
975
|
+
return this.tunnelDetailsCache.get(tunnelId) ?? {
|
|
857
976
|
configId: tunnelId,
|
|
858
977
|
configName: tunnelId
|
|
859
978
|
};
|
|
@@ -890,7 +1009,6 @@ var WsCommand = {
|
|
|
890
1009
|
var WebSocketCommandHandler = class {
|
|
891
1010
|
constructor(handler) {
|
|
892
1011
|
this.tunnelHandler = handler ?? new TunnelOperations();
|
|
893
|
-
remoteManagementWebSocketPrinter.setTunnelHandler(this.tunnelHandler);
|
|
894
1012
|
}
|
|
895
1013
|
safeParse(text) {
|
|
896
1014
|
if (!text) return void 0;
|
|
@@ -957,8 +1075,10 @@ var WebSocketCommandHandler = class {
|
|
|
957
1075
|
}
|
|
958
1076
|
}
|
|
959
1077
|
async handleStopReq(req, raw) {
|
|
1078
|
+
let tunnelID;
|
|
960
1079
|
try {
|
|
961
1080
|
const dc = StopSchema.parse(raw);
|
|
1081
|
+
tunnelID = dc.tunnelID;
|
|
962
1082
|
remoteManagementWebSocketPrinter.printStopRequested(dc.tunnelID);
|
|
963
1083
|
const result = await this.tunnelHandler.handleStop(dc.tunnelID);
|
|
964
1084
|
remoteManagementWebSocketPrinter.handleStopResult(dc.tunnelID, result);
|
|
@@ -968,6 +1088,9 @@ var WebSocketCommandHandler = class {
|
|
|
968
1088
|
printer_default.warn("Validation failed for stop request");
|
|
969
1089
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
970
1090
|
}
|
|
1091
|
+
if (tunnelID) {
|
|
1092
|
+
remoteManagementWebSocketPrinter.handleStopResult(tunnelID, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
1093
|
+
}
|
|
971
1094
|
printer_default.warn(`Error in handleStopReq error: ${String(e)}`);
|
|
972
1095
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
973
1096
|
}
|
|
@@ -987,8 +1110,10 @@ var WebSocketCommandHandler = class {
|
|
|
987
1110
|
}
|
|
988
1111
|
}
|
|
989
1112
|
async handleRestartReq(req, raw) {
|
|
1113
|
+
let tunnelID;
|
|
990
1114
|
try {
|
|
991
1115
|
const dc = RestartSchema.parse(raw);
|
|
1116
|
+
tunnelID = dc.tunnelID;
|
|
992
1117
|
remoteManagementWebSocketPrinter.printRestartRequested(dc.tunnelID);
|
|
993
1118
|
const result = await this.tunnelHandler.handleRestart(dc.tunnelID, true);
|
|
994
1119
|
remoteManagementWebSocketPrinter.handleRestartResult(dc.tunnelID, result);
|
|
@@ -998,34 +1123,51 @@ var WebSocketCommandHandler = class {
|
|
|
998
1123
|
printer_default.warn("Validation failed for restart request");
|
|
999
1124
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
1000
1125
|
}
|
|
1126
|
+
if (tunnelID) {
|
|
1127
|
+
remoteManagementWebSocketPrinter.handleRestartResult(tunnelID, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
1128
|
+
}
|
|
1001
1129
|
printer_default.warn(`Error in handleRestartReq error: ${String(e)}`);
|
|
1002
1130
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
1003
1131
|
}
|
|
1004
1132
|
}
|
|
1005
1133
|
async handleUpdateConfigReq(req, raw) {
|
|
1134
|
+
let parsedConfig;
|
|
1006
1135
|
try {
|
|
1007
1136
|
const dc = UpdateConfigSchema.parse(raw);
|
|
1137
|
+
parsedConfig = dc.tunnelConfig;
|
|
1138
|
+
remoteManagementWebSocketPrinter.printUpdateConfigRequested(dc.tunnelConfig);
|
|
1008
1139
|
const result = await this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig, true);
|
|
1140
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(dc.tunnelConfig, result);
|
|
1009
1141
|
return this.wrapResponse(result, req);
|
|
1010
1142
|
} catch (e) {
|
|
1011
1143
|
if (e instanceof z2.ZodError) {
|
|
1012
1144
|
printer_default.warn("Validation failed for updateconfig request");
|
|
1013
1145
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
1014
1146
|
}
|
|
1147
|
+
if (parsedConfig) {
|
|
1148
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(parsedConfig, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
1149
|
+
}
|
|
1015
1150
|
printer_default.warn(`Error in handleUpdateConfigReq error: ${String(e)}`);
|
|
1016
1151
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
1017
1152
|
}
|
|
1018
1153
|
}
|
|
1019
1154
|
async handleUpdateConfigV2Req(req, raw) {
|
|
1155
|
+
let parsedConfig;
|
|
1020
1156
|
try {
|
|
1021
1157
|
const dc = UpdateConfigV2Schema.parse(raw);
|
|
1158
|
+
parsedConfig = dc.tunnelConfig;
|
|
1159
|
+
remoteManagementWebSocketPrinter.printUpdateConfigRequested(dc.tunnelConfig);
|
|
1022
1160
|
const result = await this.tunnelHandler.handleUpdateConfigV2(dc.tunnelConfig, true);
|
|
1161
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(dc.tunnelConfig, result);
|
|
1023
1162
|
return this.wrapResponse(result, req);
|
|
1024
1163
|
} catch (e) {
|
|
1025
1164
|
if (e instanceof z2.ZodError) {
|
|
1026
1165
|
printer_default.warn("Validation failed for update-config-v2 request");
|
|
1027
1166
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
1028
1167
|
}
|
|
1168
|
+
if (parsedConfig) {
|
|
1169
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(parsedConfig, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
1170
|
+
}
|
|
1029
1171
|
printer_default.warn(`Error in handleUpdateConfigV2Req error: ${String(e)}`);
|
|
1030
1172
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
1031
1173
|
}
|
|
@@ -1505,16 +1647,18 @@ async function ensureDaemonRunning() {
|
|
|
1505
1647
|
const existing = getDaemonInfo();
|
|
1506
1648
|
if (existing) return existing;
|
|
1507
1649
|
if (process.versions.electron) {
|
|
1508
|
-
const { runDaemonChild } = await import("./daemonChild-
|
|
1650
|
+
const { runDaemonChild } = await import("./daemonChild-ZACX7V6J.js");
|
|
1509
1651
|
const handle = await runDaemonChild({
|
|
1510
1652
|
installSignalHandlers: false,
|
|
1511
|
-
exitOnFailure: false
|
|
1653
|
+
exitOnFailure: false,
|
|
1654
|
+
host: DaemonHost.APP
|
|
1512
1655
|
});
|
|
1513
1656
|
inProcessHandle = handle;
|
|
1514
1657
|
return getDaemonInfo() ?? {
|
|
1515
1658
|
pid: handle.pid,
|
|
1516
1659
|
port: handle.port,
|
|
1517
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1660
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1661
|
+
host: DaemonHost.APP
|
|
1518
1662
|
};
|
|
1519
1663
|
}
|
|
1520
1664
|
return startDaemon();
|
|
@@ -1525,7 +1669,7 @@ function getInProcessDaemonHandle() {
|
|
|
1525
1669
|
async function getActiveTunnelSummaries(origin = "app") {
|
|
1526
1670
|
const info = getDaemonInfo();
|
|
1527
1671
|
if (!info) return [];
|
|
1528
|
-
const { IPCClient: IPCClient2 } = await import("./ipcClient-
|
|
1672
|
+
const { IPCClient: IPCClient2 } = await import("./ipcClient-XRL2WUUM.js");
|
|
1529
1673
|
const client = new IPCClient2(info.port, origin);
|
|
1530
1674
|
let tunnels;
|
|
1531
1675
|
try {
|
|
@@ -1572,10 +1716,17 @@ async function stopDaemon() {
|
|
|
1572
1716
|
if (!info) return { ok: false, error: "No daemon is running." };
|
|
1573
1717
|
let daemonErrors = [];
|
|
1574
1718
|
try {
|
|
1575
|
-
const { IPCClient: IPCClient2 } = await import("./ipcClient-
|
|
1719
|
+
const { IPCClient: IPCClient2 } = await import("./ipcClient-XRL2WUUM.js");
|
|
1576
1720
|
const client = new IPCClient2(info.port);
|
|
1577
1721
|
const result = await client.shutdown();
|
|
1578
1722
|
logger.debug("Sent shutdown command to daemon", { result });
|
|
1723
|
+
if (result?.status === ShutdownStatus.RefusedInApp) {
|
|
1724
|
+
return {
|
|
1725
|
+
ok: false,
|
|
1726
|
+
reason: DaemonHost.APP,
|
|
1727
|
+
error: "The daemon is running inside the Pinggy app."
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1579
1730
|
if (Array.isArray(result?.errors)) daemonErrors = result.errors;
|
|
1580
1731
|
} catch (e) {
|
|
1581
1732
|
return { ok: false, error: `Failed to reach daemon: ${errorMessage(e)}` };
|
|
@@ -1861,6 +2012,13 @@ var DaemonHealth = class {
|
|
|
1861
2012
|
bindPid(pid) {
|
|
1862
2013
|
this.originalPid = pid;
|
|
1863
2014
|
}
|
|
2015
|
+
/**
|
|
2016
|
+
* Forget a previous loss verdict. Called when the client re-binds to a
|
|
2017
|
+
* live daemon via ensureDaemon():
|
|
2018
|
+
*/
|
|
2019
|
+
reset() {
|
|
2020
|
+
this.lost = false;
|
|
2021
|
+
}
|
|
1864
2022
|
isLost() {
|
|
1865
2023
|
return this.lost;
|
|
1866
2024
|
}
|
|
@@ -1965,6 +2123,7 @@ var TunnelClient = class _TunnelClient {
|
|
|
1965
2123
|
const info = await ensureDaemonRunning();
|
|
1966
2124
|
this.ipc = new IPCClient(info.port, this.origin);
|
|
1967
2125
|
this.health.bindPid(info.pid);
|
|
2126
|
+
this.health.reset();
|
|
1968
2127
|
}
|
|
1969
2128
|
static async forRemoteManagement() {
|
|
1970
2129
|
const client = new _TunnelClient({ origin: "remote" });
|
|
@@ -2029,15 +2188,30 @@ var TunnelClient = class _TunnelClient {
|
|
|
2029
2188
|
this.assertClient();
|
|
2030
2189
|
return this.ipc.getTunnel(tunnelId);
|
|
2031
2190
|
}
|
|
2032
|
-
async handleRestart(tunnelId) {
|
|
2191
|
+
async handleRestart(tunnelId, noWait) {
|
|
2033
2192
|
this.assertClient();
|
|
2034
|
-
return this.ipc.restartTunnel(tunnelId);
|
|
2193
|
+
return this.ipc.restartTunnel(tunnelId, noWait);
|
|
2035
2194
|
}
|
|
2036
2195
|
async shutdown() {
|
|
2037
2196
|
this.assertClient();
|
|
2038
2197
|
await this.ipc.shutdown();
|
|
2039
2198
|
this.close();
|
|
2040
2199
|
}
|
|
2200
|
+
/**
|
|
2201
|
+
* Tear down the daemon hosted in THIS process, if any. Stops all tunnels
|
|
2202
|
+
* and removes daemon.json + daemon-state.json so the next CLI run doesn't
|
|
2203
|
+
* "crash-recover" tunnels that died with the host application.
|
|
2204
|
+
*/
|
|
2205
|
+
shutdownInProcessDaemon() {
|
|
2206
|
+
const handle = getInProcessDaemonHandle();
|
|
2207
|
+
if (!handle) return false;
|
|
2208
|
+
try {
|
|
2209
|
+
this.close();
|
|
2210
|
+
} catch {
|
|
2211
|
+
}
|
|
2212
|
+
handle.shutdown();
|
|
2213
|
+
return true;
|
|
2214
|
+
}
|
|
2041
2215
|
async getLogLevel() {
|
|
2042
2216
|
this.assertClient();
|
|
2043
2217
|
const res = await this.ipc.getLogLevel();
|
|
@@ -21,6 +21,14 @@ var Route = {
|
|
|
21
21
|
SetTunnelLogging: "POST /config/tunnel-logging",
|
|
22
22
|
GetLogPaths: "GET /logs/paths"
|
|
23
23
|
};
|
|
24
|
+
var DaemonHost = {
|
|
25
|
+
CLI: "cli",
|
|
26
|
+
APP: "app"
|
|
27
|
+
};
|
|
28
|
+
var ShutdownStatus = {
|
|
29
|
+
ShuttingDown: "shutting_down",
|
|
30
|
+
RefusedInApp: "refused_in_app"
|
|
31
|
+
};
|
|
24
32
|
var SessionMode = {
|
|
25
33
|
Foreground: "foreground",
|
|
26
34
|
Detached: "detached"
|
|
@@ -54,8 +62,8 @@ var IPCClient = class {
|
|
|
54
62
|
async stopTunnel(tunnelid) {
|
|
55
63
|
return this.call(Route.StopTunnel, { tunnelid });
|
|
56
64
|
}
|
|
57
|
-
async restartTunnel(tunnelid) {
|
|
58
|
-
return this.call(Route.RestartTunnel, { tunnelid });
|
|
65
|
+
async restartTunnel(tunnelid, noWait) {
|
|
66
|
+
return this.call(Route.RestartTunnel, { tunnelid, noWait });
|
|
59
67
|
}
|
|
60
68
|
// v1 operations (used by remote management via daemon)
|
|
61
69
|
async startTunnelV1(config, mode, noWait) {
|
|
@@ -159,6 +167,8 @@ var IPCClient = class {
|
|
|
159
167
|
|
|
160
168
|
export {
|
|
161
169
|
Route,
|
|
170
|
+
DaemonHost,
|
|
171
|
+
ShutdownStatus,
|
|
162
172
|
SessionMode,
|
|
163
173
|
IPCClient
|
|
164
174
|
};
|
|
@@ -22,8 +22,8 @@ import {
|
|
|
22
22
|
validateName,
|
|
23
23
|
validateNameForStorage,
|
|
24
24
|
validateNameStrict
|
|
25
|
-
} from "./chunk-
|
|
26
|
-
import "./chunk-
|
|
25
|
+
} from "./chunk-YWXADADB.js";
|
|
26
|
+
import "./chunk-P7WNBN7F.js";
|
|
27
27
|
import "./chunk-GBYF2H4H.js";
|
|
28
28
|
export {
|
|
29
29
|
CONFIG_VERBS,
|