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.
@@ -3,7 +3,7 @@ import {
3
3
  IPCClient,
4
4
  SessionMode,
5
5
  ShutdownStatus
6
- } from "./chunk-5YHD6M35.js";
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-DLNUDW6G.js";
15
+ } from "./chunk-PQSLSIDA.js";
16
16
  import {
17
17
  logger
18
- } from "./chunk-7G6SJEEA.js";
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
- optional: OptionalSchema.optional()
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.optional?.serve);
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.optional?.serve);
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
- this.tunnelManager.updateConfig(updateOpts).catch((err) => {
536
- logger.error("No-wait updateConfig failed", { configid: config.configid, err: String(err) });
537
- });
538
- return this.buildPendingTunnelResponse(existing.tunnelid, existing.tunnelConfig, config.configid, existing.tunnelName, existing.serve);
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
- this.tunnelManager.updateConfig(config).catch((err) => {
554
- logger.error("No-wait updateConfigV2 failed", { configId: config.configId, err: String(err) });
555
- });
556
- return this.buildPendingTunnelResponseV2(existing.tunnelid, existing.tunnelConfig, config, config.configId, existing.tunnelName, existing.serve);
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 PENDING_START_TIMEOUT_MS = 5 * 60 * 1e3;
704
- var RemoteManagementWebSocketPrinter = class {
723
+ var PENDING_TIMEOUT_MS = 5 * 60 * 1e3;
724
+ var PendingTracker = class {
705
725
  constructor() {
706
- this.pendingStarts = /* @__PURE__ */ new Map();
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
- setTunnelHandler(tunnelHandler) {
709
- this.tunnelHandler = tunnelHandler;
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.latestPendingConfigId = entry.configId;
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 (this.latestPendingConfigId === configId) {
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.latestPendingConfigId && requestedConfigId !== this.latestPendingConfigId) {
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.set(requestedConfigId, pending);
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
- printer_default.stopSpinnerSuccess("Restarted tunnel with config name: " + details.configName);
780
- if (result.remoteurls?.length > 0) {
781
- printer_default.info(pico.cyanBright("Remote URLs:"));
782
- (result.remoteurls ?? []).forEach(
783
- (url) => printer_default.print(" " + pico.magentaBright(url))
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
- if (!Array.isArray(result) || this.pendingStarts.size === 0 || !this.latestPendingConfigId) {
896
+ this.cleanupExpiredPendingActions();
897
+ if (!Array.isArray(result)) {
790
898
  return;
791
899
  }
792
900
  for (const tunnel of result) {
793
- const pending = this.findPendingStart(tunnel);
794
- if (!pending) {
795
- continue;
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
- if (pending.configId !== this.latestPendingConfigId) {
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
- pending.tunnelId = tunnel.tunnelid;
801
- this.pendingStarts.set(pending.configId, pending);
934
+ entry.tunnelId = tunnel.tunnelid;
935
+ tracker.upsert(entry);
802
936
  if (tunnel.remoteurls.length > 0) {
803
- this.completePendingStart(pending, tunnel.remoteurls);
937
+ onComplete(entry, tunnel.remoteurls);
804
938
  continue;
805
939
  }
806
940
  if (tunnel.status.state === "exited" /* Exited */) {
807
- const reason = tunnel.status.errormsg || "Tunnel exited before a public URL was assigned";
808
- this.pendingStarts.delete(pending.configId);
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.latestPendingConfigId && entry.configId !== this.latestPendingConfigId) {
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
- const now = Date.now();
829
- for (const [configId, entry] of this.pendingStarts.entries()) {
830
- if (now - entry.queuedAt <= PENDING_START_TIMEOUT_MS) {
831
- continue;
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
- this.pendingStarts.delete(configId);
834
- printer_default.warn(`Timed out while waiting for tunnel URL for config name: ${entry.configName}`);
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
- findPendingStart(tunnel) {
839
- const configId = this.getConfigIdFromTunnel(tunnel);
840
- const byConfigId = this.pendingStarts.get(configId);
841
- if (byConfigId) {
842
- return byConfigId;
843
- }
844
- for (const entry of this.pendingStarts.values()) {
845
- if (entry.tunnelId === tunnel.tunnelid) {
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
- return void 0;
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-WIRWKBKB.js");
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-Z2RXDQUQ.js");
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-Z2RXDQUQ.js");
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();