pinggy 0.5.3 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +596 -0
- package/README.md +4 -0
- package/dist/{TunnelManager-OPUMAZFX.js → TunnelManager-ZSVX7U3Z.js} +2 -2
- package/dist/{TunnelTui-QZEWWH2H.js → TunnelTui-S35BNVPO.js} +85 -16
- package/dist/{chunk-GK2PQ7KX.js → chunk-HN4YLM7U.js} +227 -74
- package/dist/{chunk-BBPR2LJB.js → chunk-OH6FSLYF.js} +39 -9
- package/dist/{chunk-7G6SJEEA.js → chunk-P7WNBN7F.js} +1 -1
- package/dist/{chunk-DLNUDW6G.js → chunk-PQSLSIDA.js} +67 -13
- package/dist/{chunk-5YHD6M35.js → chunk-YTJEV27N.js} +2 -2
- package/dist/{chunk-WM7I57AA.js → chunk-YWXADADB.js} +1 -1
- package/dist/{configStore-HP3NXPH4.js → configStore-2AZRILVC.js} +2 -2
- package/dist/{daemonChild-WIRWKBKB.js → daemonChild-3ONQO2XI.js} +6 -6
- package/dist/index.cjs +420 -114
- package/dist/index.d.cts +24 -17
- package/dist/index.d.ts +24 -17
- package/dist/index.js +6 -6
- package/dist/{ipcClient-Z2RXDQUQ.js → ipcClient-XRL2WUUM.js} +1 -1
- package/dist/{main-B2EBHFN2.js → main-WXFARRMN.js} +6 -6
- package/dist/workers/file_serve_worker.js +1 -1
- package/jest.config.cjs +1 -0
- package/package.json +2 -2
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
IPCClient,
|
|
4
4
|
SessionMode,
|
|
5
5
|
ShutdownStatus
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-YTJEV27N.js";
|
|
7
7
|
import {
|
|
8
8
|
TunnelAlreadyRunningError,
|
|
9
9
|
TunnelManager,
|
|
@@ -12,10 +12,10 @@ import {
|
|
|
12
12
|
getVersion,
|
|
13
13
|
isValidPort,
|
|
14
14
|
printer_default
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-PQSLSIDA.js";
|
|
16
16
|
import {
|
|
17
17
|
logger
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-P7WNBN7F.js";
|
|
19
19
|
import {
|
|
20
20
|
getDaemonInfoPath,
|
|
21
21
|
getDaemonLogPath
|
|
@@ -129,7 +129,7 @@ var RemoteManagementStatus = {
|
|
|
129
129
|
};
|
|
130
130
|
|
|
131
131
|
// src/remote_management/remote_schema.ts
|
|
132
|
-
import { TunnelType } from "@pinggy/pinggy";
|
|
132
|
+
import { HaProxyVersion, TunnelType } from "@pinggy/pinggy";
|
|
133
133
|
import { z } from "zod";
|
|
134
134
|
var HeaderModificationSchema = z.object({
|
|
135
135
|
key: z.string(),
|
|
@@ -209,7 +209,7 @@ var UpdateConfigSchema = z.object({
|
|
|
209
209
|
var ForwardingEntryV2Schema = z.object({
|
|
210
210
|
listenAddress: z.string().optional(),
|
|
211
211
|
address: z.string(),
|
|
212
|
-
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)
|
|
213
213
|
});
|
|
214
214
|
var OptionalSchema = z.object({
|
|
215
215
|
sniServerName: z.string().optional(),
|
|
@@ -253,7 +253,22 @@ var TunnelConfigV1Schema = z.object({
|
|
|
253
253
|
httpsOnly: z.boolean().optional(),
|
|
254
254
|
originalRequestUrl: z.boolean().optional(),
|
|
255
255
|
allowPreflight: z.boolean().optional(),
|
|
256
|
-
|
|
256
|
+
haProxy: z.enum([HaProxyVersion.V1, HaProxyVersion.V2]).optional(),
|
|
257
|
+
serve: z.string().optional(),
|
|
258
|
+
optional: z.record(z.string(), z.unknown()).optional()
|
|
259
|
+
}).superRefine((config, ctx) => {
|
|
260
|
+
if (!config.haProxy) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const entries = Array.isArray(config.forwarding) ? config.forwarding : [];
|
|
264
|
+
const primaryType = entries.length > 0 ? entries[0].type : void 0;
|
|
265
|
+
if (primaryType !== TunnelType.Tcp) {
|
|
266
|
+
ctx.addIssue({
|
|
267
|
+
code: "custom",
|
|
268
|
+
path: ["haProxy"],
|
|
269
|
+
message: `haProxy can only be used with "tcp" tunnels, but the first forwarding rule has type: ${primaryType ?? TunnelType.Http}`
|
|
270
|
+
});
|
|
271
|
+
}
|
|
257
272
|
});
|
|
258
273
|
var TUNNEL_CONFIG_V1_KEYS = Object.keys(
|
|
259
274
|
TunnelConfigV1Schema.shape
|
|
@@ -286,6 +301,7 @@ function pinggyOptionsToTunnelConfigV1(opts, configStoredInCli) {
|
|
|
286
301
|
httpsOnly: opts.httpsOnly ?? false,
|
|
287
302
|
originalRequestUrl: opts.originalRequestUrl ?? false,
|
|
288
303
|
allowPreflight: opts.allowPreflight ?? false,
|
|
304
|
+
haProxy: opts.haProxy,
|
|
289
305
|
optional: opts.optional || {}
|
|
290
306
|
};
|
|
291
307
|
}
|
|
@@ -506,11 +522,11 @@ var TunnelOperations = class {
|
|
|
506
522
|
startPromise.catch((err) => {
|
|
507
523
|
logger.error("No-wait startTunnel failed", { tunnelid, err: String(err) });
|
|
508
524
|
});
|
|
509
|
-
return this.buildPendingTunnelResponseV2(tunnelid, tunnelConfig, config, config.configId, config.name, config.
|
|
525
|
+
return this.buildPendingTunnelResponseV2(tunnelid, tunnelConfig, config, config.configId, config.name, config.serve);
|
|
510
526
|
}
|
|
511
527
|
await startPromise;
|
|
512
528
|
const tunnelPconfig = await this.tunnelManager.getTunnelConfig("", tunnelid);
|
|
513
|
-
return this.buildTunnelResponseV2(tunnelid, tunnelPconfig, config, config.configId, config.name, config.
|
|
529
|
+
return this.buildTunnelResponseV2(tunnelid, tunnelPconfig, config, config.configId, config.name, config.serve);
|
|
514
530
|
} catch (err) {
|
|
515
531
|
if (err instanceof TunnelAlreadyRunningError) {
|
|
516
532
|
return this.error(ErrorCode.TunnelAlreadyRunningError, err, err.message);
|
|
@@ -532,10 +548,12 @@ var TunnelOperations = class {
|
|
|
532
548
|
if (noWait) {
|
|
533
549
|
const existing = this.tunnelManager.getManagedTunnel(config.configid);
|
|
534
550
|
if (!existing.tunnelConfig) throw new Error("Invalid tunnel state before configuration update");
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
551
|
+
if (!existing.isStopped) {
|
|
552
|
+
this.tunnelManager.updateConfig(updateOpts).catch((err) => {
|
|
553
|
+
logger.error("No-wait updateConfig failed", { configid: config.configid, err: String(err) });
|
|
554
|
+
});
|
|
555
|
+
return this.buildPendingTunnelResponse(existing.tunnelid, existing.tunnelConfig, config.configid, existing.tunnelName, existing.serve);
|
|
556
|
+
}
|
|
539
557
|
}
|
|
540
558
|
const tunnel = await this.tunnelManager.updateConfig(updateOpts);
|
|
541
559
|
if (!tunnel.instance || !tunnel.tunnelConfig)
|
|
@@ -550,10 +568,12 @@ var TunnelOperations = class {
|
|
|
550
568
|
if (noWait) {
|
|
551
569
|
const existing = this.tunnelManager.getManagedTunnel(config.configId);
|
|
552
570
|
if (!existing.tunnelConfig) throw new Error("Invalid tunnel state before configuration update");
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
571
|
+
if (!existing.isStopped) {
|
|
572
|
+
this.tunnelManager.updateConfig(config).catch((err) => {
|
|
573
|
+
logger.error("No-wait updateConfigV2 failed", { configId: config.configId, err: String(err) });
|
|
574
|
+
});
|
|
575
|
+
return this.buildPendingTunnelResponseV2(existing.tunnelid, existing.tunnelConfig, config, config.configId, existing.tunnelName, existing.serve);
|
|
576
|
+
}
|
|
557
577
|
}
|
|
558
578
|
const tunnel = await this.tunnelManager.updateConfig(config);
|
|
559
579
|
if (!tunnel.instance || !tunnel.tunnelConfig)
|
|
@@ -700,13 +720,71 @@ import WebSocket from "ws";
|
|
|
700
720
|
|
|
701
721
|
// src/remote_management/websocket_printer.ts
|
|
702
722
|
import pico from "picocolors";
|
|
703
|
-
var
|
|
704
|
-
var
|
|
723
|
+
var PENDING_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
724
|
+
var PendingTracker = class {
|
|
705
725
|
constructor() {
|
|
706
|
-
this.
|
|
726
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
727
|
+
}
|
|
728
|
+
queue(entry) {
|
|
729
|
+
this.latestConfigId = entry.configId;
|
|
730
|
+
this.entries.set(entry.configId, entry);
|
|
731
|
+
}
|
|
732
|
+
/** Store an entry without claiming the latest cursor. */
|
|
733
|
+
upsert(entry) {
|
|
734
|
+
this.entries.set(entry.configId, entry);
|
|
735
|
+
}
|
|
736
|
+
get(configId) {
|
|
737
|
+
return this.entries.get(configId);
|
|
738
|
+
}
|
|
739
|
+
find(configId, tunnelId) {
|
|
740
|
+
const byConfigId = this.entries.get(configId);
|
|
741
|
+
if (byConfigId) {
|
|
742
|
+
return byConfigId;
|
|
743
|
+
}
|
|
744
|
+
if (!tunnelId) {
|
|
745
|
+
return void 0;
|
|
746
|
+
}
|
|
747
|
+
for (const entry of this.entries.values()) {
|
|
748
|
+
if (entry.tunnelId === tunnelId) {
|
|
749
|
+
return entry;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return void 0;
|
|
753
|
+
}
|
|
754
|
+
delete(configId) {
|
|
755
|
+
this.entries.delete(configId);
|
|
756
|
+
if (this.latestConfigId === configId) {
|
|
757
|
+
this.latestConfigId = void 0;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
get latest() {
|
|
761
|
+
return this.latestConfigId;
|
|
762
|
+
}
|
|
763
|
+
get hasLatest() {
|
|
764
|
+
return this.entries.size > 0 && this.latestConfigId !== void 0;
|
|
707
765
|
}
|
|
708
|
-
|
|
709
|
-
this.
|
|
766
|
+
isLatest(configId) {
|
|
767
|
+
return configId === this.latestConfigId;
|
|
768
|
+
}
|
|
769
|
+
cleanupExpired(onTimeout) {
|
|
770
|
+
const now = Date.now();
|
|
771
|
+
for (const [configId, entry] of this.entries) {
|
|
772
|
+
if (now - entry.queuedAt <= PENDING_TIMEOUT_MS) {
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
const wasLatest = this.isLatest(configId);
|
|
776
|
+
this.delete(configId);
|
|
777
|
+
onTimeout(entry, wasLatest);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
var RemoteManagementWebSocketPrinter = class {
|
|
782
|
+
constructor() {
|
|
783
|
+
this.pendingStarts = new PendingTracker();
|
|
784
|
+
this.pendingActions = new PendingTracker();
|
|
785
|
+
// tunnelId -> details, fed by monitorList. Stop/restart requests only carry
|
|
786
|
+
// a tunnelId; without this the spinner shows the raw id.
|
|
787
|
+
this.tunnelDetailsCache = /* @__PURE__ */ new Map();
|
|
710
788
|
}
|
|
711
789
|
queueStart(config) {
|
|
712
790
|
this.cleanupExpiredPendingStarts();
|
|
@@ -715,24 +793,23 @@ var RemoteManagementWebSocketPrinter = class {
|
|
|
715
793
|
configName: this.getConfigNameFromRequest(config),
|
|
716
794
|
queuedAt: Date.now()
|
|
717
795
|
};
|
|
718
|
-
this.
|
|
719
|
-
this.pendingStarts.set(entry.configId, entry);
|
|
796
|
+
this.pendingStarts.queue(entry);
|
|
720
797
|
printer_default.startSpinner("Starting tunnel with config name: " + entry.configName);
|
|
721
798
|
}
|
|
722
799
|
failQueuedStart(config, reason) {
|
|
723
800
|
const configId = this.getConfigIdFromRequest(config);
|
|
724
801
|
const pending = this.pendingStarts.get(configId);
|
|
725
802
|
const configName = pending?.configName || this.getConfigNameFromRequest(config);
|
|
803
|
+
const wasLatest = this.pendingStarts.isLatest(configId);
|
|
726
804
|
this.pendingStarts.delete(configId);
|
|
727
|
-
if (
|
|
728
|
-
this.latestPendingConfigId = void 0;
|
|
805
|
+
if (wasLatest) {
|
|
729
806
|
printer_default.stopSpinnerFail(`Failed to start tunnel with config name: ${configName}. ${reason}`);
|
|
730
807
|
}
|
|
731
808
|
}
|
|
732
809
|
handleStartResult(config, result) {
|
|
733
810
|
this.cleanupExpiredPendingStarts();
|
|
734
811
|
const requestedConfigId = this.getConfigIdFromRequest(config);
|
|
735
|
-
if (this.
|
|
812
|
+
if (this.pendingStarts.latest && !this.pendingStarts.isLatest(requestedConfigId)) {
|
|
736
813
|
this.pendingStarts.delete(requestedConfigId);
|
|
737
814
|
return;
|
|
738
815
|
}
|
|
@@ -740,20 +817,20 @@ var RemoteManagementWebSocketPrinter = class {
|
|
|
740
817
|
this.failQueuedStart(config, result.message);
|
|
741
818
|
return;
|
|
742
819
|
}
|
|
743
|
-
const configId = this.getConfigIdFromTunnel(result);
|
|
744
820
|
const pending = this.pendingStarts.get(requestedConfigId) || {
|
|
745
821
|
configId: requestedConfigId,
|
|
746
822
|
configName: this.getConfigNameFromRequest(config),
|
|
747
823
|
queuedAt: Date.now()
|
|
748
824
|
};
|
|
749
825
|
pending.tunnelId = result.tunnelid;
|
|
750
|
-
this.pendingStarts.
|
|
826
|
+
this.pendingStarts.upsert(pending);
|
|
751
827
|
if (result.remoteurls.length > 0) {
|
|
752
828
|
this.completePendingStart(pending, result.remoteurls);
|
|
753
829
|
}
|
|
754
830
|
}
|
|
755
831
|
printStopRequested(tunnelId) {
|
|
756
832
|
const details = this.resolveTunnelDetails(tunnelId);
|
|
833
|
+
this.clearPendingAction(details.configId, tunnelId);
|
|
757
834
|
printer_default.startSpinner("Stopping tunnel with config name: " + details.configName);
|
|
758
835
|
}
|
|
759
836
|
handleStopResult(tunnelId, result) {
|
|
@@ -767,6 +844,7 @@ var RemoteManagementWebSocketPrinter = class {
|
|
|
767
844
|
}
|
|
768
845
|
printRestartRequested(tunnelId) {
|
|
769
846
|
const details = this.resolveTunnelDetails(tunnelId);
|
|
847
|
+
this.clearPendingAction(details.configId, tunnelId);
|
|
770
848
|
printer_default.startSpinner("Restarting tunnel with config name: " + details.configName);
|
|
771
849
|
}
|
|
772
850
|
handleRestartResult(tunnelId, result) {
|
|
@@ -776,48 +854,101 @@ var RemoteManagementWebSocketPrinter = class {
|
|
|
776
854
|
printer_default.stopSpinnerFail("Failed to restart tunnel with config name: " + details.configName);
|
|
777
855
|
return;
|
|
778
856
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
857
|
+
this.queuePendingAction("restart", details.configId, details.configName, result.tunnelid);
|
|
858
|
+
}
|
|
859
|
+
printUpdateConfigRequested(config) {
|
|
860
|
+
const configName = this.getConfigNameFromRequest(config);
|
|
861
|
+
this.clearPendingAction(this.getConfigIdFromRequest(config));
|
|
862
|
+
printer_default.startSpinner("Updating tunnel configuration for: " + configName);
|
|
863
|
+
}
|
|
864
|
+
handleUpdateConfigResult(config, result) {
|
|
865
|
+
const configId = this.getConfigIdFromRequest(config);
|
|
866
|
+
const configName = this.getConfigNameFromRequest(config);
|
|
867
|
+
if (isErrorResponse(result)) {
|
|
868
|
+
printer_default.stopSpinnerFail(`Failed to update tunnel configuration for: ${configName}. ${result.message}`);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
if (result.status.state === "exited" /* Exited */) {
|
|
872
|
+
printer_default.stopSpinnerSuccess(`Tunnel configuration updated: ${configName}.`);
|
|
873
|
+
return;
|
|
785
874
|
}
|
|
875
|
+
this.queuePendingAction("update", configId, configName, result.tunnelid);
|
|
876
|
+
}
|
|
877
|
+
clearPendingAction(configId, tunnelId) {
|
|
878
|
+
const entry = this.pendingActions.find(configId, tunnelId);
|
|
879
|
+
if (!entry) {
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
this.pendingActions.delete(entry.configId);
|
|
883
|
+
}
|
|
884
|
+
queuePendingAction(action, configId, configName, tunnelId) {
|
|
885
|
+
this.cleanupExpiredPendingActions();
|
|
886
|
+
this.pendingActions.queue({ action, configId, configName, queuedAt: Date.now(), tunnelId });
|
|
887
|
+
}
|
|
888
|
+
printActionComplete(action, configName, urls) {
|
|
889
|
+
const message = action === "restart" ? `Restarted tunnel with config name: ${configName}` : `Tunnel configuration updated and restarted: ${configName}`;
|
|
890
|
+
printer_default.stopSpinnerSuccess(message);
|
|
891
|
+
printer_default.info(pico.cyanBright("Remote URLs:"));
|
|
892
|
+
urls.forEach((url) => printer_default.print(" " + pico.magentaBright(url)));
|
|
786
893
|
}
|
|
787
894
|
monitorList(result) {
|
|
788
895
|
this.cleanupExpiredPendingStarts();
|
|
789
|
-
|
|
896
|
+
this.cleanupExpiredPendingActions();
|
|
897
|
+
if (!Array.isArray(result)) {
|
|
790
898
|
return;
|
|
791
899
|
}
|
|
792
900
|
for (const tunnel of result) {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
901
|
+
this.tunnelDetailsCache.set(tunnel.tunnelid, {
|
|
902
|
+
configId: this.getConfigIdFromTunnel(tunnel),
|
|
903
|
+
configName: this.getConfigNameFromTunnel(tunnel)
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
this.resolvePendingFromList(
|
|
907
|
+
result,
|
|
908
|
+
this.pendingStarts,
|
|
909
|
+
(entry, urls) => this.completePendingStart(entry, urls),
|
|
910
|
+
(entry, reason) => printer_default.stopSpinnerFail(`Tunnel start did not complete for config name: ${entry.configName}. ${reason}`)
|
|
911
|
+
);
|
|
912
|
+
this.resolvePendingFromList(
|
|
913
|
+
result,
|
|
914
|
+
this.pendingActions,
|
|
915
|
+
(entry, urls) => {
|
|
916
|
+
this.pendingActions.delete(entry.configId);
|
|
917
|
+
this.printActionComplete(entry.action, entry.configName, urls);
|
|
918
|
+
},
|
|
919
|
+
(entry, reason) => {
|
|
920
|
+
const verb = entry.action === "restart" ? "Restart" : "Configuration update";
|
|
921
|
+
printer_default.stopSpinnerFail(`${verb} did not complete for config name: ${entry.configName}. ${reason}`);
|
|
796
922
|
}
|
|
797
|
-
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
resolvePendingFromList(tunnels, tracker, onComplete, onExited) {
|
|
926
|
+
if (!tracker.hasLatest) {
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
for (const tunnel of tunnels) {
|
|
930
|
+
const entry = tracker.find(this.getConfigIdFromTunnel(tunnel), tunnel.tunnelid);
|
|
931
|
+
if (!entry || !tracker.isLatest(entry.configId)) {
|
|
798
932
|
continue;
|
|
799
933
|
}
|
|
800
|
-
|
|
801
|
-
|
|
934
|
+
entry.tunnelId = tunnel.tunnelid;
|
|
935
|
+
tracker.upsert(entry);
|
|
802
936
|
if (tunnel.remoteurls.length > 0) {
|
|
803
|
-
|
|
937
|
+
onComplete(entry, tunnel.remoteurls);
|
|
804
938
|
continue;
|
|
805
939
|
}
|
|
806
940
|
if (tunnel.status.state === "exited" /* Exited */) {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
this.latestPendingConfigId = void 0;
|
|
810
|
-
printer_default.stopSpinnerFail(`Tunnel start did not complete for config name: ${pending.configName}. ${reason}`);
|
|
941
|
+
tracker.delete(entry.configId);
|
|
942
|
+
onExited(entry, tunnel.status.errormsg || "Tunnel exited before a public URL was assigned");
|
|
811
943
|
}
|
|
812
944
|
}
|
|
813
945
|
}
|
|
814
946
|
completePendingStart(entry, urls) {
|
|
815
|
-
if (this.
|
|
947
|
+
if (this.pendingStarts.latest && !this.pendingStarts.isLatest(entry.configId)) {
|
|
816
948
|
this.pendingStarts.delete(entry.configId);
|
|
817
949
|
return;
|
|
818
950
|
}
|
|
819
951
|
this.pendingStarts.delete(entry.configId);
|
|
820
|
-
this.latestPendingConfigId = void 0;
|
|
821
952
|
printer_default.stopSpinnerSuccess(`Tunnel started with config name: ${entry.configName}.`);
|
|
822
953
|
printer_default.info(pico.cyanBright("Remote URLs:"));
|
|
823
954
|
(urls ?? []).forEach(
|
|
@@ -825,28 +956,27 @@ var RemoteManagementWebSocketPrinter = class {
|
|
|
825
956
|
);
|
|
826
957
|
}
|
|
827
958
|
cleanupExpiredPendingStarts() {
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
if (
|
|
831
|
-
|
|
959
|
+
this.pendingStarts.cleanupExpired((entry, wasLatest) => {
|
|
960
|
+
const message = `Timed out while waiting for tunnel URL for config name: ${entry.configName}`;
|
|
961
|
+
if (wasLatest) {
|
|
962
|
+
printer_default.stopSpinnerFail(message);
|
|
963
|
+
} else {
|
|
964
|
+
printer_default.warn(message);
|
|
832
965
|
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
logger.warn("Pending websocket start entry expired", { configId, tunnelId: entry.tunnelId });
|
|
836
|
-
}
|
|
966
|
+
logger.warn("Pending websocket start entry expired", { configId: entry.configId, tunnelId: entry.tunnelId });
|
|
967
|
+
});
|
|
837
968
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
return entry;
|
|
969
|
+
cleanupExpiredPendingActions() {
|
|
970
|
+
this.pendingActions.cleanupExpired((entry, wasLatest) => {
|
|
971
|
+
const verb = entry.action === "restart" ? "restart" : "configuration update";
|
|
972
|
+
const message = `Timed out while waiting for tunnel URL after ${verb} for config name: ${entry.configName}`;
|
|
973
|
+
if (wasLatest) {
|
|
974
|
+
printer_default.stopSpinnerFail(message);
|
|
975
|
+
} else {
|
|
976
|
+
printer_default.warn(message);
|
|
847
977
|
}
|
|
848
|
-
|
|
849
|
-
|
|
978
|
+
logger.warn("Pending websocket action entry expired", { configId: entry.configId, tunnelId: entry.tunnelId, action: entry.action });
|
|
979
|
+
});
|
|
850
980
|
}
|
|
851
981
|
resolveTunnelDetails(tunnelId, result) {
|
|
852
982
|
if (result && !isErrorResponse(result)) {
|
|
@@ -855,7 +985,7 @@ var RemoteManagementWebSocketPrinter = class {
|
|
|
855
985
|
configName: this.getConfigNameFromTunnel(result)
|
|
856
986
|
};
|
|
857
987
|
}
|
|
858
|
-
return {
|
|
988
|
+
return this.tunnelDetailsCache.get(tunnelId) ?? {
|
|
859
989
|
configId: tunnelId,
|
|
860
990
|
configName: tunnelId
|
|
861
991
|
};
|
|
@@ -892,7 +1022,6 @@ var WsCommand = {
|
|
|
892
1022
|
var WebSocketCommandHandler = class {
|
|
893
1023
|
constructor(handler) {
|
|
894
1024
|
this.tunnelHandler = handler ?? new TunnelOperations();
|
|
895
|
-
remoteManagementWebSocketPrinter.setTunnelHandler(this.tunnelHandler);
|
|
896
1025
|
}
|
|
897
1026
|
safeParse(text) {
|
|
898
1027
|
if (!text) return void 0;
|
|
@@ -959,8 +1088,10 @@ var WebSocketCommandHandler = class {
|
|
|
959
1088
|
}
|
|
960
1089
|
}
|
|
961
1090
|
async handleStopReq(req, raw) {
|
|
1091
|
+
let tunnelID;
|
|
962
1092
|
try {
|
|
963
1093
|
const dc = StopSchema.parse(raw);
|
|
1094
|
+
tunnelID = dc.tunnelID;
|
|
964
1095
|
remoteManagementWebSocketPrinter.printStopRequested(dc.tunnelID);
|
|
965
1096
|
const result = await this.tunnelHandler.handleStop(dc.tunnelID);
|
|
966
1097
|
remoteManagementWebSocketPrinter.handleStopResult(dc.tunnelID, result);
|
|
@@ -970,6 +1101,9 @@ var WebSocketCommandHandler = class {
|
|
|
970
1101
|
printer_default.warn("Validation failed for stop request");
|
|
971
1102
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
972
1103
|
}
|
|
1104
|
+
if (tunnelID) {
|
|
1105
|
+
remoteManagementWebSocketPrinter.handleStopResult(tunnelID, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
1106
|
+
}
|
|
973
1107
|
printer_default.warn(`Error in handleStopReq error: ${String(e)}`);
|
|
974
1108
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
975
1109
|
}
|
|
@@ -989,8 +1123,10 @@ var WebSocketCommandHandler = class {
|
|
|
989
1123
|
}
|
|
990
1124
|
}
|
|
991
1125
|
async handleRestartReq(req, raw) {
|
|
1126
|
+
let tunnelID;
|
|
992
1127
|
try {
|
|
993
1128
|
const dc = RestartSchema.parse(raw);
|
|
1129
|
+
tunnelID = dc.tunnelID;
|
|
994
1130
|
remoteManagementWebSocketPrinter.printRestartRequested(dc.tunnelID);
|
|
995
1131
|
const result = await this.tunnelHandler.handleRestart(dc.tunnelID, true);
|
|
996
1132
|
remoteManagementWebSocketPrinter.handleRestartResult(dc.tunnelID, result);
|
|
@@ -1000,34 +1136,51 @@ var WebSocketCommandHandler = class {
|
|
|
1000
1136
|
printer_default.warn("Validation failed for restart request");
|
|
1001
1137
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
1002
1138
|
}
|
|
1139
|
+
if (tunnelID) {
|
|
1140
|
+
remoteManagementWebSocketPrinter.handleRestartResult(tunnelID, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
1141
|
+
}
|
|
1003
1142
|
printer_default.warn(`Error in handleRestartReq error: ${String(e)}`);
|
|
1004
1143
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
1005
1144
|
}
|
|
1006
1145
|
}
|
|
1007
1146
|
async handleUpdateConfigReq(req, raw) {
|
|
1147
|
+
let parsedConfig;
|
|
1008
1148
|
try {
|
|
1009
1149
|
const dc = UpdateConfigSchema.parse(raw);
|
|
1150
|
+
parsedConfig = dc.tunnelConfig;
|
|
1151
|
+
remoteManagementWebSocketPrinter.printUpdateConfigRequested(dc.tunnelConfig);
|
|
1010
1152
|
const result = await this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig, true);
|
|
1153
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(dc.tunnelConfig, result);
|
|
1011
1154
|
return this.wrapResponse(result, req);
|
|
1012
1155
|
} catch (e) {
|
|
1013
1156
|
if (e instanceof z2.ZodError) {
|
|
1014
1157
|
printer_default.warn("Validation failed for updateconfig request");
|
|
1015
1158
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
1016
1159
|
}
|
|
1160
|
+
if (parsedConfig) {
|
|
1161
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(parsedConfig, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
1162
|
+
}
|
|
1017
1163
|
printer_default.warn(`Error in handleUpdateConfigReq error: ${String(e)}`);
|
|
1018
1164
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
1019
1165
|
}
|
|
1020
1166
|
}
|
|
1021
1167
|
async handleUpdateConfigV2Req(req, raw) {
|
|
1168
|
+
let parsedConfig;
|
|
1022
1169
|
try {
|
|
1023
1170
|
const dc = UpdateConfigV2Schema.parse(raw);
|
|
1171
|
+
parsedConfig = dc.tunnelConfig;
|
|
1172
|
+
remoteManagementWebSocketPrinter.printUpdateConfigRequested(dc.tunnelConfig);
|
|
1024
1173
|
const result = await this.tunnelHandler.handleUpdateConfigV2(dc.tunnelConfig, true);
|
|
1174
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(dc.tunnelConfig, result);
|
|
1025
1175
|
return this.wrapResponse(result, req);
|
|
1026
1176
|
} catch (e) {
|
|
1027
1177
|
if (e instanceof z2.ZodError) {
|
|
1028
1178
|
printer_default.warn("Validation failed for update-config-v2 request");
|
|
1029
1179
|
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
1030
1180
|
}
|
|
1181
|
+
if (parsedConfig) {
|
|
1182
|
+
remoteManagementWebSocketPrinter.handleUpdateConfigResult(parsedConfig, newErrorResponse({ code: ErrorCode.InternalServerError, message: errorMessage(e) }));
|
|
1183
|
+
}
|
|
1031
1184
|
printer_default.warn(`Error in handleUpdateConfigV2Req error: ${String(e)}`);
|
|
1032
1185
|
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
1033
1186
|
}
|
|
@@ -1507,7 +1660,7 @@ async function ensureDaemonRunning() {
|
|
|
1507
1660
|
const existing = getDaemonInfo();
|
|
1508
1661
|
if (existing) return existing;
|
|
1509
1662
|
if (process.versions.electron) {
|
|
1510
|
-
const { runDaemonChild } = await import("./daemonChild-
|
|
1663
|
+
const { runDaemonChild } = await import("./daemonChild-3ONQO2XI.js");
|
|
1511
1664
|
const handle = await runDaemonChild({
|
|
1512
1665
|
installSignalHandlers: false,
|
|
1513
1666
|
exitOnFailure: false,
|
|
@@ -1529,7 +1682,7 @@ function getInProcessDaemonHandle() {
|
|
|
1529
1682
|
async function getActiveTunnelSummaries(origin = "app") {
|
|
1530
1683
|
const info = getDaemonInfo();
|
|
1531
1684
|
if (!info) return [];
|
|
1532
|
-
const { IPCClient: IPCClient2 } = await import("./ipcClient-
|
|
1685
|
+
const { IPCClient: IPCClient2 } = await import("./ipcClient-XRL2WUUM.js");
|
|
1533
1686
|
const client = new IPCClient2(info.port, origin);
|
|
1534
1687
|
let tunnels;
|
|
1535
1688
|
try {
|
|
@@ -1576,7 +1729,7 @@ async function stopDaemon() {
|
|
|
1576
1729
|
if (!info) return { ok: false, error: "No daemon is running." };
|
|
1577
1730
|
let daemonErrors = [];
|
|
1578
1731
|
try {
|
|
1579
|
-
const { IPCClient: IPCClient2 } = await import("./ipcClient-
|
|
1732
|
+
const { IPCClient: IPCClient2 } = await import("./ipcClient-XRL2WUUM.js");
|
|
1580
1733
|
const client = new IPCClient2(info.port);
|
|
1581
1734
|
const result = await client.shutdown();
|
|
1582
1735
|
logger.debug("Sent shutdown command to daemon", { result });
|
|
@@ -2048,9 +2201,9 @@ var TunnelClient = class _TunnelClient {
|
|
|
2048
2201
|
this.assertClient();
|
|
2049
2202
|
return this.ipc.getTunnel(tunnelId);
|
|
2050
2203
|
}
|
|
2051
|
-
async handleRestart(tunnelId) {
|
|
2204
|
+
async handleRestart(tunnelId, noWait) {
|
|
2052
2205
|
this.assertClient();
|
|
2053
|
-
return this.ipc.restartTunnel(tunnelId);
|
|
2206
|
+
return this.ipc.restartTunnel(tunnelId, noWait);
|
|
2054
2207
|
}
|
|
2055
2208
|
async shutdown() {
|
|
2056
2209
|
this.assertClient();
|