pinggy 0.3.7 → 0.3.9

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/dist/index.cjs CHANGED
@@ -118,6 +118,9 @@ var init_printer = __esm({
118
118
  console.error(import_picocolors2.default.red(import_picocolors2.default.bold("\u2716 Error:")), import_picocolors2.default.red(msg));
119
119
  process.exit(1);
120
120
  }
121
+ static red(message) {
122
+ return import_picocolors2.default.red(message);
123
+ }
121
124
  static warn(message) {
122
125
  console.warn(import_picocolors2.default.yellow(import_picocolors2.default.bold("\u26A0 Warning:")), import_picocolors2.default.yellow(message));
123
126
  }
@@ -283,22 +286,32 @@ function isValidPort(p) {
283
286
  return Number.isInteger(p) && p > 0 && p < 65536;
284
287
  }
285
288
  function getVersion() {
286
- return pkg.version ?? "";
289
+ try {
290
+ const packageJsonPath = (0, import_path3.join)(__dirname, "../package.json");
291
+ const pkg = JSON.parse((0, import_fs3.readFileSync)(packageJsonPath, "utf-8"));
292
+ return pkg.version ?? "";
293
+ } catch (error) {
294
+ printer_default.error("Error reading version info");
295
+ return "";
296
+ }
287
297
  }
288
- var import_module, import_crypto, require2, pkg;
298
+ var import_fs3, import_crypto, import_url, import_path3, __filename2, __dirname;
289
299
  var init_util = __esm({
290
300
  "src/utils/util.ts"() {
291
301
  "use strict";
292
302
  init_cjs_shims();
293
- import_module = require("module");
303
+ import_fs3 = require("fs");
294
304
  import_crypto = require("crypto");
295
- require2 = (0, import_module.createRequire)(importMetaUrl);
296
- pkg = require2("../package.json");
305
+ import_url = require("url");
306
+ import_path3 = require("path");
307
+ init_printer();
308
+ __filename2 = (0, import_url.fileURLToPath)(importMetaUrl);
309
+ __dirname = (0, import_path3.dirname)(__filename2);
297
310
  }
298
311
  });
299
312
 
300
313
  // src/tunnel_manager/TunnelManager.ts
301
- var import_pinggy2, import_node_path, import_node_worker_threads, import_node_url, __filename2, __dirname, TunnelManager;
314
+ var import_pinggy2, import_node_path, import_node_worker_threads, import_node_url, __filename3, __dirname2, TunnelManager;
302
315
  var init_TunnelManager = __esm({
303
316
  "src/tunnel_manager/TunnelManager.ts"() {
304
317
  "use strict";
@@ -310,8 +323,8 @@ var init_TunnelManager = __esm({
310
323
  import_node_url = require("url");
311
324
  init_printer();
312
325
  init_util();
313
- __filename2 = (0, import_node_url.fileURLToPath)(importMetaUrl);
314
- __dirname = import_node_path.default.dirname(__filename2);
326
+ __filename3 = (0, import_node_url.fileURLToPath)(importMetaUrl);
327
+ __dirname2 = import_node_path.default.dirname(__filename3);
315
328
  TunnelManager = class _TunnelManager {
316
329
  constructor() {
317
330
  this.tunnelsByTunnelId = /* @__PURE__ */ new Map();
@@ -319,6 +332,7 @@ var init_TunnelManager = __esm({
319
332
  this.tunnelStats = /* @__PURE__ */ new Map();
320
333
  this.tunnelStatsListeners = /* @__PURE__ */ new Map();
321
334
  this.tunnelErrorListeners = /* @__PURE__ */ new Map();
335
+ this.tunnelPollingErrorListeners = /* @__PURE__ */ new Map();
322
336
  this.tunnelDisconnectListeners = /* @__PURE__ */ new Map();
323
337
  this.tunnelWorkerErrorListeners = /* @__PURE__ */ new Map();
324
338
  this.tunnelStartListeners = /* @__PURE__ */ new Map();
@@ -335,12 +349,9 @@ var init_TunnelManager = __esm({
335
349
  }
336
350
  /**
337
351
  * Creates a new managed tunnel instance with the given configuration.
338
- * Builds the config with forwarding rules and creates the tunnel instance.
352
+ * Optionally builds the config with forwarding rules based on buildConfig flag.
339
353
  *
340
354
  * @param config - The tunnel configuration options
341
- * @param config.configid - Unique identifier for the tunnel configuration
342
- * @param config.tunnelid - Optional custom tunnel identifier. If not provided, a random UUID will be generated
343
- * @param config.additionalForwarding - Optional array of additional forwarding configurations
344
355
  *
345
356
  * @throws {Error} When configId is invalid or empty
346
357
  * @throws {Error} When a tunnel with the given configId already exists
@@ -349,24 +360,19 @@ var init_TunnelManager = __esm({
349
360
  * status information, and statistics
350
361
  */
351
362
  async createTunnel(config) {
352
- const { configid, additionalForwarding, tunnelName } = config;
353
- if (configid === void 0 || configid.trim().length === 0) {
354
- throw new Error(`Invalid configId: "${configid}"`);
355
- }
356
- if (this.tunnelsByConfigId.has(configid)) {
357
- throw new Error(`Tunnel with configId "${configid}" already exists`);
363
+ const { configId, tunnelid: requestedTunnelId, tunnelName, name, serve } = config;
364
+ const tunnelid = requestedTunnelId || getRandomId();
365
+ const autoReconnect = config.autoReconnect || false;
366
+ if (!configId || typeof configId !== "string" || configId.trim() === "") {
367
+ throw new Error("configId is required and must be a non-empty string");
358
368
  }
359
- const tunnelid = config.tunnelid || getRandomId();
360
- const configWithForwarding = this.buildPinggyConfig(config, additionalForwarding);
361
369
  return this._createTunnelWithProcessedConfig({
362
- configid,
370
+ configId,
363
371
  tunnelid,
364
- tunnelName,
372
+ tunnelName: tunnelName || name,
365
373
  originalConfig: config,
366
- configWithForwarding,
367
- additionalForwarding,
368
- serve: config.serve,
369
- autoReconnect: config.autoReconnect !== void 0 ? config.autoReconnect : false
374
+ serve,
375
+ autoReconnect
370
376
  });
371
377
  }
372
378
  /**
@@ -380,7 +386,8 @@ var init_TunnelManager = __esm({
380
386
  async _createTunnelWithProcessedConfig(params) {
381
387
  let instance;
382
388
  try {
383
- instance = await import_pinggy2.pinggy.createTunnel(params.configWithForwarding);
389
+ logger.debug("Creating tunnel instance with processed config", params.originalConfig);
390
+ instance = await import_pinggy2.pinggy.createTunnel(params.originalConfig);
384
391
  } catch (e) {
385
392
  logger.error("Error creating tunnel instance:", e);
386
393
  throw e;
@@ -388,12 +395,10 @@ var init_TunnelManager = __esm({
388
395
  const now = (/* @__PURE__ */ new Date()).toISOString();
389
396
  const managed = {
390
397
  tunnelid: params.tunnelid,
391
- configid: params.configid,
398
+ configId: params.configId,
392
399
  tunnelName: params.tunnelName,
393
400
  instance,
394
401
  tunnelConfig: params.originalConfig,
395
- configWithForwarding: params.configWithForwarding,
396
- additionalForwarding: params.additionalForwarding,
397
402
  serve: params.serve,
398
403
  warnings: [],
399
404
  isStopped: false,
@@ -407,6 +412,7 @@ var init_TunnelManager = __esm({
407
412
  });
408
413
  this.setupStatsCallback(params.tunnelid, managed);
409
414
  this.setupErrorCallback(params.tunnelid, managed);
415
+ this.setupTunnelPollingErrorCallback(params.tunnelid, managed);
410
416
  this.setupDisconnectCallback(params.tunnelid, managed);
411
417
  this.setupWillReconnectCallback(params.tunnelid, managed);
412
418
  this.setupReconnectingCallback(params.tunnelid, managed);
@@ -414,44 +420,10 @@ var init_TunnelManager = __esm({
414
420
  this.setupReconnectionFailedCallback(params.tunnelid, managed);
415
421
  this.setUpTunnelWorkerErrorCallback(params.tunnelid, managed);
416
422
  this.tunnelsByTunnelId.set(params.tunnelid, managed);
417
- this.tunnelsByConfigId.set(params.configid, managed);
418
- logger.info("Tunnel created", { configid: params.configid, tunnelid: params.tunnelid });
423
+ this.tunnelsByConfigId.set(params.configId, managed);
424
+ logger.info("Tunnel created", { configId: params.configId, tunnelId: params.tunnelid });
419
425
  return managed;
420
426
  }
421
- /**
422
- * Builds the Pinggy configuration by merging the default forwarding rule
423
- * with additional forwarding rules from additionalForwarding array.
424
- *
425
- * @param config - The base Pinggy configuration
426
- * @param additionalForwarding - Optional array of additional forwarding rules
427
- * @returns Modified PinggyOptions
428
- */
429
- buildPinggyConfig(config, additionalForwarding) {
430
- const forwardingRules = [];
431
- if (config.forwarding) {
432
- forwardingRules.push({
433
- type: config.tunnelType && config.tunnelType[0] || "http",
434
- address: config.forwarding
435
- });
436
- }
437
- if (Array.isArray(additionalForwarding) && additionalForwarding.length > 0) {
438
- for (const rule of additionalForwarding) {
439
- if (rule && rule.localDomain && rule.localPort && rule.remoteDomain && isValidPort(rule.localPort)) {
440
- const forwardingRule = {
441
- type: rule.protocol,
442
- // In Future we can make this dynamic based on user input
443
- address: `${rule.localDomain}:${rule.localPort}`,
444
- listenAddress: rule.remotePort && isValidPort(rule.remotePort) ? `${rule.remoteDomain}:${rule.remotePort}` : rule.remoteDomain
445
- };
446
- forwardingRules.push(forwardingRule);
447
- }
448
- }
449
- }
450
- return {
451
- ...config,
452
- forwarding: forwardingRules.length > 0 ? forwardingRules : config.forwarding
453
- };
454
- }
455
427
  /**
456
428
  * Start a tunnel that was created but not yet started
457
429
  */
@@ -463,7 +435,7 @@ var init_TunnelManager = __esm({
463
435
  try {
464
436
  urls = await managed.instance.start();
465
437
  } catch (error) {
466
- logger.error("Failed to start tunnel", { tunnelId, error });
438
+ logger.warn("Failed to start tunnel", { tunnelId, error });
467
439
  throw error;
468
440
  }
469
441
  logger.info("Tunnel started", { tunnelId, urls });
@@ -499,7 +471,7 @@ var init_TunnelManager = __esm({
499
471
  stopTunnel(tunnelId) {
500
472
  const managed = this.tunnelsByTunnelId.get(tunnelId);
501
473
  if (!managed) throw new Error(`Tunnel "${tunnelId}" not found`);
502
- logger.info("Stopping tunnel", { tunnelId, configId: managed.configid });
474
+ logger.info("Stopping tunnel", { tunnelId, configId: managed.configId });
503
475
  try {
504
476
  managed.instance.stop();
505
477
  if (managed.serveWorker) {
@@ -511,6 +483,7 @@ var init_TunnelManager = __esm({
511
483
  this.tunnelStats.delete(tunnelId);
512
484
  this.tunnelStatsListeners.delete(tunnelId);
513
485
  this.tunnelErrorListeners.delete(tunnelId);
486
+ this.tunnelPollingErrorListeners.delete(tunnelId);
514
487
  this.tunnelDisconnectListeners.delete(tunnelId);
515
488
  this.tunnelWorkerErrorListeners.delete(tunnelId);
516
489
  this.tunnelStartListeners.delete(tunnelId);
@@ -522,8 +495,8 @@ var init_TunnelManager = __esm({
522
495
  managed.warnings = managed.warnings ?? [];
523
496
  managed.isStopped = true;
524
497
  managed.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
525
- logger.info("Tunnel stopped", { tunnelId, configId: managed.configid });
526
- return { configid: managed.configid, tunnelid: managed.tunnelid };
498
+ logger.info("Tunnel stopped", { tunnelId, configId: managed.configId });
499
+ return { configId: managed.configId, tunnelid: managed.tunnelid };
527
500
  } catch (error) {
528
501
  logger.error("Failed to stop tunnel", { tunnelId, error });
529
502
  throw error;
@@ -556,11 +529,10 @@ var init_TunnelManager = __esm({
556
529
  const tunnelList = await Promise.all(Array.from(this.tunnelsByTunnelId.values()).map(async (tunnel) => {
557
530
  return {
558
531
  tunnelid: tunnel.tunnelid,
559
- configid: tunnel.configid,
532
+ configId: tunnel.configId,
560
533
  tunnelName: tunnel.tunnelName,
561
534
  tunnelConfig: tunnel.tunnelConfig,
562
535
  remoteurls: !tunnel.isStopped ? await this.getTunnelUrls(tunnel.tunnelid) : [],
563
- additionalForwarding: tunnel.additionalForwarding,
564
536
  serve: tunnel.serve
565
537
  };
566
538
  }));
@@ -601,6 +573,15 @@ var init_TunnelManager = __esm({
601
573
  this.tunnelsByConfigId.clear();
602
574
  this.tunnelStats.clear();
603
575
  this.tunnelStatsListeners.clear();
576
+ this.tunnelErrorListeners.clear();
577
+ this.tunnelPollingErrorListeners.clear();
578
+ this.tunnelDisconnectListeners.clear();
579
+ this.tunnelWorkerErrorListeners.clear();
580
+ this.tunnelStartListeners.clear();
581
+ this.tunnelWillReconnectListeners.clear();
582
+ this.tunnelReconnectingListeners.clear();
583
+ this.tunnelReconnectionCompletedListeners.clear();
584
+ this.tunnelReconnectionFailedListeners.clear();
604
585
  logger.info("All tunnels stopped and cleared");
605
586
  }
606
587
  /**
@@ -621,7 +602,7 @@ var init_TunnelManager = __esm({
621
602
  return false;
622
603
  }
623
604
  this._cleanupTunnelRecords(managed);
624
- logger.info("Removed stopped tunnel records", { tunnelId, configId: managed.configid });
605
+ logger.info("Removed stopped tunnel records", { tunnelId, configId: managed.configId });
625
606
  return true;
626
607
  }
627
608
  /**
@@ -648,6 +629,7 @@ var init_TunnelManager = __esm({
648
629
  this.tunnelStats.delete(managed.tunnelid);
649
630
  this.tunnelStatsListeners.delete(managed.tunnelid);
650
631
  this.tunnelErrorListeners.delete(managed.tunnelid);
632
+ this.tunnelPollingErrorListeners.delete(managed.tunnelid);
651
633
  this.tunnelDisconnectListeners.delete(managed.tunnelid);
652
634
  this.tunnelWorkerErrorListeners.delete(managed.tunnelid);
653
635
  this.tunnelStartListeners.delete(managed.tunnelid);
@@ -656,7 +638,7 @@ var init_TunnelManager = __esm({
656
638
  this.tunnelReconnectionCompletedListeners.delete(managed.tunnelid);
657
639
  this.tunnelReconnectionFailedListeners.delete(managed.tunnelid);
658
640
  this.tunnelsByTunnelId.delete(managed.tunnelid);
659
- this.tunnelsByConfigId.delete(managed.configid);
641
+ this.tunnelsByConfigId.delete(managed.configId);
660
642
  } catch (e) {
661
643
  logger.warn("Failed cleaning up tunnel records", { tunnelId: managed.tunnelid, error: e });
662
644
  }
@@ -717,21 +699,20 @@ var init_TunnelManager = __esm({
717
699
  }
718
700
  logger.info("Initiating tunnel restart", {
719
701
  tunnelId: tunnelid,
720
- configId: existingTunnel.configid
702
+ configId: existingTunnel.configId
721
703
  });
722
704
  try {
723
705
  const tunnelName = existingTunnel.tunnelName;
724
- const currentConfigId = existingTunnel.configid;
706
+ const currentConfigId = existingTunnel.configId;
725
707
  const currentConfig = existingTunnel.tunnelConfig;
726
- const configWithForwarding = existingTunnel.configWithForwarding;
727
- const additionalForwarding = existingTunnel.additionalForwarding;
728
708
  const currentServe = existingTunnel.serve;
729
709
  const autoReconnect = existingTunnel.autoReconnect || false;
730
710
  this.tunnelsByTunnelId.delete(tunnelid);
731
- this.tunnelsByConfigId.delete(existingTunnel.configid);
711
+ this.tunnelsByConfigId.delete(existingTunnel.configId);
732
712
  this.tunnelStats.delete(tunnelid);
733
713
  this.tunnelStatsListeners.delete(tunnelid);
734
714
  this.tunnelErrorListeners.delete(tunnelid);
715
+ this.tunnelPollingErrorListeners.delete(tunnelid);
735
716
  this.tunnelDisconnectListeners.delete(tunnelid);
736
717
  this.tunnelWorkerErrorListeners.delete(tunnelid);
737
718
  this.tunnelStartListeners.delete(tunnelid);
@@ -740,12 +721,10 @@ var init_TunnelManager = __esm({
740
721
  this.tunnelReconnectionCompletedListeners.delete(tunnelid);
741
722
  this.tunnelReconnectionFailedListeners.delete(tunnelid);
742
723
  const newTunnel = await this._createTunnelWithProcessedConfig({
743
- configid: currentConfigId,
724
+ configId: currentConfigId,
744
725
  tunnelid,
745
726
  tunnelName,
746
727
  originalConfig: currentConfig,
747
- configWithForwarding,
748
- additionalForwarding,
749
728
  serve: currentServe,
750
729
  autoReconnect
751
730
  });
@@ -774,20 +753,18 @@ var init_TunnelManager = __esm({
774
753
  * @throws Error if the tunnel is not found or if the update process fails
775
754
  */
776
755
  async updateConfig(newConfig) {
777
- const { configid, tunnelName: newTunnelName, additionalForwarding } = newConfig;
778
- if (!configid || configid.trim().length === 0) {
779
- throw new Error(`Invalid configid: "${configid}"`);
756
+ const { configId, tunnelName: newTunnelName } = newConfig;
757
+ if (!configId || configId.trim().length === 0) {
758
+ throw new Error(`Invalid configId: "${configId}"`);
780
759
  }
781
- const existingTunnel = this.tunnelsByConfigId.get(configid);
760
+ const existingTunnel = this.tunnelsByConfigId.get(configId);
782
761
  if (!existingTunnel) {
783
- throw new Error(`Tunnel with config id "${configid}" not found`);
762
+ throw new Error(`Tunnel with config id "${configId}" not found`);
784
763
  }
785
764
  const isStopped = existingTunnel.isStopped;
786
765
  const currentTunnelConfig = existingTunnel.tunnelConfig;
787
- const currentConfigWithForwarding = existingTunnel.configWithForwarding;
788
766
  const currentTunnelId = existingTunnel.tunnelid;
789
- const currentTunnelConfigId = existingTunnel.configid;
790
- const currentAdditionalForwarding = existingTunnel.additionalForwarding;
767
+ const currentTunnelConfigId = existingTunnel.configId;
791
768
  const currentTunnelName = existingTunnel.tunnelName;
792
769
  const currentServe = existingTunnel.serve;
793
770
  const currentAutoReconnect = existingTunnel.autoReconnect || false;
@@ -799,22 +776,19 @@ var init_TunnelManager = __esm({
799
776
  this.tunnelsByConfigId.delete(currentTunnelConfigId);
800
777
  const mergedBaseConfig = {
801
778
  ...newConfig,
802
- configid,
779
+ configId,
803
780
  tunnelName: newTunnelName !== void 0 ? newTunnelName : currentTunnelName,
804
781
  serve: newConfig.serve !== void 0 ? newConfig.serve : currentServe
805
782
  };
806
- const newConfigWithForwarding = this.buildPinggyConfig(
807
- mergedBaseConfig,
808
- additionalForwarding !== void 0 ? additionalForwarding : currentAdditionalForwarding
809
- );
783
+ const effectiveServe = newConfig.serve !== void 0 ? newConfig.serve : currentServe;
784
+ const effectiveTunnelName = newTunnelName !== void 0 ? newTunnelName : currentTunnelName;
785
+ let configWithForwarding;
810
786
  const newTunnel = await this._createTunnelWithProcessedConfig({
811
- configid,
787
+ configId,
812
788
  tunnelid: currentTunnelId,
813
- tunnelName: newTunnelName !== void 0 ? newTunnelName : currentTunnelName,
789
+ tunnelName: effectiveTunnelName,
814
790
  originalConfig: mergedBaseConfig,
815
- configWithForwarding: newConfigWithForwarding,
816
- additionalForwarding: additionalForwarding !== void 0 ? additionalForwarding : currentAdditionalForwarding,
817
- serve: newConfig.serve !== void 0 ? newConfig.serve : currentServe,
791
+ serve: effectiveServe,
818
792
  autoReconnect: currentAutoReconnect
819
793
  });
820
794
  if (!isStopped) {
@@ -822,23 +796,21 @@ var init_TunnelManager = __esm({
822
796
  }
823
797
  logger.info("Tunnel configuration updated", {
824
798
  tunnelId: newTunnel.tunnelid,
825
- configId: newTunnel.configid,
799
+ configId: newTunnel.configId,
826
800
  isStopped
827
801
  });
828
802
  return newTunnel;
829
803
  } catch (error) {
830
804
  logger.error("Error updating tunnel configuration", {
831
- configId: configid,
805
+ configId,
832
806
  error: error instanceof Error ? error.message : String(error)
833
807
  });
834
808
  try {
835
809
  const originalTunnel = await this._createTunnelWithProcessedConfig({
836
- configid: currentTunnelConfigId,
810
+ configId: currentTunnelConfigId,
837
811
  tunnelid: currentTunnelId,
838
812
  tunnelName: currentTunnelName,
839
813
  originalConfig: currentTunnelConfig,
840
- configWithForwarding: currentConfigWithForwarding,
841
- additionalForwarding: currentAdditionalForwarding,
842
814
  serve: currentServe,
843
815
  autoReconnect: currentAutoReconnect
844
816
  });
@@ -954,6 +926,19 @@ var init_TunnelManager = __esm({
954
926
  logger.info("Error listener registered for tunnel", { tunnelId, listenerId });
955
927
  return listenerId;
956
928
  }
929
+ async registerPollingErrorListener(tunnelId, listener) {
930
+ const managed = this.tunnelsByTunnelId.get(tunnelId);
931
+ if (!managed) {
932
+ throw new Error(`Tunnel "${tunnelId}" not found`);
933
+ }
934
+ if (!this.tunnelPollingErrorListeners.has(tunnelId)) {
935
+ this.tunnelPollingErrorListeners.set(tunnelId, /* @__PURE__ */ new Map());
936
+ }
937
+ const listenerId = getRandomId();
938
+ this.tunnelPollingErrorListeners.get(tunnelId).set(listenerId, listener);
939
+ logger.info("Polling error listener registered for tunnel", { tunnelId, listenerId });
940
+ return listenerId;
941
+ }
957
942
  async registerDisconnectListener(tunnelId, listener) {
958
943
  const managed = this.tunnelsByTunnelId.get(tunnelId);
959
944
  if (!managed) {
@@ -1085,6 +1070,22 @@ var init_TunnelManager = __esm({
1085
1070
  logger.warn("Attempted to deregister non-existent error listener", { tunnelId, listenerId });
1086
1071
  }
1087
1072
  }
1073
+ deregisterPollingErrorListener(tunnelId, listenerId) {
1074
+ const listeners = this.tunnelPollingErrorListeners.get(tunnelId);
1075
+ if (!listeners) {
1076
+ logger.warn("No polling error listeners found for tunnel", { tunnelId });
1077
+ return;
1078
+ }
1079
+ const removed = listeners.delete(listenerId);
1080
+ if (removed) {
1081
+ logger.info("Polling error listener deregistered", { tunnelId, listenerId });
1082
+ if (listeners.size === 0) {
1083
+ this.tunnelPollingErrorListeners.delete(tunnelId);
1084
+ }
1085
+ } else {
1086
+ logger.warn("Attempted to deregister non-existent polling error listener", { tunnelId, listenerId });
1087
+ }
1088
+ }
1088
1089
  deregisterDisconnectListener(tunnelId, listenerId) {
1089
1090
  const listeners = this.tunnelDisconnectListeners.get(tunnelId);
1090
1091
  if (!listeners) {
@@ -1202,6 +1203,38 @@ var init_TunnelManager = __esm({
1202
1203
  logger.warn("Failed to set up stats callback", { tunnelId, error });
1203
1204
  }
1204
1205
  }
1206
+ setupTunnelPollingErrorCallback(tunnelId, managed) {
1207
+ try {
1208
+ const callback = ({ error }) => {
1209
+ try {
1210
+ const errorMessage = error instanceof Error ? error.message : String(error);
1211
+ logger.info("Tunnel reported polling error", { tunnelId, errorMessage });
1212
+ this.notifyPollingErrorListeners(tunnelId, errorMessage);
1213
+ } catch (e) {
1214
+ logger.warn("Error handling tunnel polling error callback", { tunnelId, e });
1215
+ }
1216
+ };
1217
+ managed.instance.setPollingErrorCallback(callback);
1218
+ logger.debug("Tunnel polling error callback set up for tunnel", { tunnelId });
1219
+ } catch (error) {
1220
+ logger.warn("Failed to set up tunnel polling error callback", { tunnelId, error });
1221
+ }
1222
+ }
1223
+ notifyPollingErrorListeners(tunnelId, errorMsg) {
1224
+ try {
1225
+ const listeners = this.tunnelPollingErrorListeners.get(tunnelId);
1226
+ if (!listeners) return;
1227
+ for (const [id, listener] of listeners) {
1228
+ try {
1229
+ listener(tunnelId, errorMsg);
1230
+ } catch (err) {
1231
+ logger.debug("Error in polling-error-listener callback", { listenerId: id, tunnelId, err });
1232
+ }
1233
+ }
1234
+ } catch (err) {
1235
+ logger.debug("Failed to notify polling error listeners", { tunnelId, err });
1236
+ }
1237
+ }
1205
1238
  notifyErrorListeners(tunnelId, errorMsg, isFatal) {
1206
1239
  try {
1207
1240
  const listeners = this.tunnelErrorListeners.get(tunnelId);
@@ -1474,9 +1507,9 @@ var init_TunnelManager = __esm({
1474
1507
  }
1475
1508
  startStaticFileServer(managed) {
1476
1509
  try {
1477
- const __filename3 = (0, import_node_url.fileURLToPath)(importMetaUrl);
1478
- const __dirname2 = import_node_path.default.dirname(__filename3);
1479
- const fileServerWorkerPath = import_node_path.default.join(__dirname2, "workers", "file_serve_worker.cjs");
1510
+ const __filename4 = (0, import_node_url.fileURLToPath)(importMetaUrl);
1511
+ const __dirname3 = import_node_path.default.dirname(__filename4);
1512
+ const fileServerWorkerPath = import_node_path.default.join(__dirname3, "workers", "file_serve_worker.cjs");
1480
1513
  const staticServerWorker = new import_node_worker_threads.Worker(fileServerWorkerPath, {
1481
1514
  workerData: {
1482
1515
  dir: managed.serve,
@@ -1628,15 +1661,57 @@ var init_types = __esm({
1628
1661
  });
1629
1662
 
1630
1663
  // src/remote_management/remote_schema.ts
1664
+ function pinggyOptionsToTunnelConfigV1(opts, configStoredInCli) {
1665
+ const parsedTokens = opts.bearerTokenAuth ? Array.isArray(opts.bearerTokenAuth) ? opts.bearerTokenAuth : JSON.parse(opts.bearerTokenAuth) : [];
1666
+ return {
1667
+ version: configStoredInCli.version || "1.0",
1668
+ name: configStoredInCli.name || "",
1669
+ configId: configStoredInCli.configId || "",
1670
+ serverAddress: opts.serverAddress || "a.pinggy.io:443",
1671
+ token: opts.token || "",
1672
+ autoReconnect: opts.autoReconnect ?? true,
1673
+ force: opts.force ?? false,
1674
+ webDebugger: opts.webDebugger || "",
1675
+ forwarding: opts.forwarding ? opts.forwarding : "",
1676
+ ipWhitelist: opts.ipWhitelist ? Array.isArray(opts.ipWhitelist) ? opts.ipWhitelist : JSON.parse(opts.ipWhitelist) : [],
1677
+ basicAuth: opts.basicAuth && Object.keys(opts.basicAuth).length ? opts.basicAuth : void 0,
1678
+ bearerTokenAuth: parsedTokens.length ? parsedTokens : void 0,
1679
+ headerModification: opts.headerModification || [],
1680
+ reverseProxy: opts.reverseProxy ?? false,
1681
+ xForwardedFor: !!opts.xForwardedFor,
1682
+ httpsOnly: opts.httpsOnly ?? false,
1683
+ originalRequestUrl: opts.originalRequestUrl ?? false,
1684
+ allowPreflight: opts.allowPreflight ?? false,
1685
+ optional: opts.optional || {}
1686
+ };
1687
+ }
1631
1688
  function tunnelConfigToPinggyOptions(config) {
1689
+ const forwardingData = [];
1690
+ forwardingData.push({
1691
+ address: `${config.forwardedhost}:${config.localport}`,
1692
+ type: config.type || import_pinggy3.TunnelType.Http
1693
+ // Default to HTTP for the primary forwarding entry
1694
+ });
1695
+ if (config.additionalForwarding && Array.isArray(config.additionalForwarding)) {
1696
+ config.additionalForwarding.forEach((entry) => {
1697
+ if (entry.localDomain && entry.localPort && entry.remoteDomain) {
1698
+ const listenAddress = entry.remotePort && isValidPort(entry.remotePort) ? `${entry.remoteDomain}:${entry.remotePort}` : entry.remoteDomain;
1699
+ forwardingData.push({
1700
+ address: `${entry.localDomain}:${entry.localPort}`,
1701
+ listenAddress,
1702
+ type: import_pinggy3.TunnelType.Http
1703
+ });
1704
+ }
1705
+ });
1706
+ }
1632
1707
  return {
1633
1708
  token: config.token || "",
1634
1709
  serverAddress: config.serveraddress || "free.pinggy.io",
1635
- forwarding: `${config.forwardedhost || "localhost"}:${config.localport}`,
1710
+ forwarding: forwardingData,
1636
1711
  webDebugger: config.webdebuggerport ? `localhost:${config.webdebuggerport}` : "",
1637
1712
  ipWhitelist: config.ipwhitelist || [],
1638
1713
  basicAuth: config.basicauth ? config.basicauth : [],
1639
- bearerTokenAuth: config.bearerauth ? [config.bearerauth] : [],
1714
+ bearerTokenAuth: config.bearerauth || [],
1640
1715
  headerModification: config.headermodification,
1641
1716
  xForwardedFor: !!config.xff,
1642
1717
  httpsOnly: config.httpsOnly,
@@ -1650,18 +1725,38 @@ function tunnelConfigToPinggyOptions(config) {
1650
1725
  }
1651
1726
  };
1652
1727
  }
1653
- function pinggyOptionsToTunnelConfig(opts, configid, configName, localserverTls, greetMsg, additionalForwarding, serve) {
1654
- const forwarding = Array.isArray(opts.forwarding) ? String(opts.forwarding[0].address).replace("//", "").replace(/\/$/, "") : String(opts.forwarding).replace("//", "").replace(/\/$/, "");
1655
- const parsedForwardedHost = forwarding.split(":").length == 3 ? forwarding.split(":")[1] : forwarding.split(":")[0];
1656
- const parsedLocalPort = forwarding.split(":").length == 3 ? parseInt(forwarding.split(":")[2], 10) : parseInt(forwarding.split(":")[1], 10);
1657
- const tunnelType = (Array.isArray(opts.forwarding) ? opts.forwarding[0]?.type : void 0) ?? import_pinggy3.TunnelType.Http;
1728
+ function pinggyOptionsToTunnelConfig(opts, configid, configName, localserverTls, greetMsg, serve) {
1729
+ let primaryEntry;
1730
+ let additionalEntries = [];
1731
+ if (Array.isArray(opts.forwarding)) {
1732
+ primaryEntry = opts.forwarding.find((e) => !e.listenAddress) ?? opts.forwarding[0];
1733
+ additionalEntries = opts.forwarding.filter(
1734
+ (e) => e !== primaryEntry && Boolean(e.listenAddress)
1735
+ );
1736
+ }
1737
+ const forwarding = primaryEntry ? String(primaryEntry.address) : String(opts.forwarding);
1738
+ const [parsedForwardedHost, portStr] = forwarding.split(":");
1739
+ const parsedLocalPort = parseInt(portStr, 10);
1740
+ const tunnelType = primaryEntry?.type ?? import_pinggy3.TunnelType.Http;
1741
+ const additionalForwarding = additionalEntries.map((e) => {
1742
+ const [localDomain, localPortStr] = String(e.address).split(":");
1743
+ const [remoteDomain, remotePortStr] = String(e.listenAddress).split(":");
1744
+ const localPort = parseInt(localPortStr, 10);
1745
+ const remotePort = parseInt(remotePortStr, 10);
1746
+ return {
1747
+ localDomain,
1748
+ localPort: isNaN(localPort) ? 0 : localPort,
1749
+ remoteDomain,
1750
+ remotePort: isNaN(remotePort) ? 0 : remotePort
1751
+ };
1752
+ });
1658
1753
  const parsedTokens = opts.bearerTokenAuth ? Array.isArray(opts.bearerTokenAuth) ? opts.bearerTokenAuth : JSON.parse(opts.bearerTokenAuth) : [];
1659
1754
  return {
1660
1755
  allowPreflight: opts.allowPreflight ?? false,
1661
1756
  allowpreflight: opts.allowPreflight ?? false,
1662
1757
  autoreconnect: opts.autoReconnect ?? false,
1663
1758
  basicauth: opts.basicAuth && Object.keys(opts.basicAuth).length ? opts.basicAuth : null,
1664
- bearerauth: parsedTokens.length ? parsedTokens.join(",") : null,
1759
+ bearerauth: parsedTokens.length ? [parsedTokens.join(",")] : null,
1665
1760
  configid,
1666
1761
  configname: configName,
1667
1762
  greetmsg: greetMsg || "",
@@ -1690,16 +1785,17 @@ function pinggyOptionsToTunnelConfig(opts, configid, configName, localserverTls,
1690
1785
  serve: serve || ""
1691
1786
  };
1692
1787
  }
1693
- var import_pinggy3, import_zod, HeaderModificationSchema, AdditionalForwardingSchema, TunnelConfigSchema, StartSchema, StopSchema, GetSchema, RestartSchema, UpdateConfigSchema;
1788
+ var import_pinggy3, import_zod, HeaderModificationSchema, AdditionalForwardingSchema, TunnelConfigSchema, StartSchema, StopSchema, GetSchema, RestartSchema, UpdateConfigSchema, ForwardingEntryV2Schema, TunnelConfigV1Schema, StartV2Schema, UpdateConfigV2Schema;
1694
1789
  var init_remote_schema = __esm({
1695
1790
  "src/remote_management/remote_schema.ts"() {
1696
1791
  "use strict";
1697
1792
  init_cjs_shims();
1698
1793
  import_pinggy3 = require("@pinggy/pinggy");
1699
1794
  import_zod = require("zod");
1795
+ init_util();
1700
1796
  HeaderModificationSchema = import_zod.z.object({
1701
1797
  key: import_zod.z.string(),
1702
- value: import_zod.z.array(import_zod.z.string()).optional(),
1798
+ value: import_zod.z.array(import_zod.z.string()).nullable().optional(),
1703
1799
  type: import_zod.z.enum(["add", "remove", "update"])
1704
1800
  });
1705
1801
  AdditionalForwardingSchema = import_zod.z.object({
@@ -1715,7 +1811,7 @@ var init_remote_schema = __esm({
1715
1811
  // legacy key
1716
1812
  autoreconnect: import_zod.z.boolean(),
1717
1813
  basicauth: import_zod.z.array(import_zod.z.object({ username: import_zod.z.string(), password: import_zod.z.string() })).nullable(),
1718
- bearerauth: import_zod.z.string().nullable(),
1814
+ bearerauth: import_zod.z.array(import_zod.z.string()).nullable(),
1719
1815
  configid: import_zod.z.string(),
1720
1816
  configname: import_zod.z.string(),
1721
1817
  greetmsg: import_zod.z.string().optional(),
@@ -1772,11 +1868,56 @@ var init_remote_schema = __esm({
1772
1868
  UpdateConfigSchema = import_zod.z.object({
1773
1869
  tunnelConfig: TunnelConfigSchema
1774
1870
  });
1871
+ ForwardingEntryV2Schema = import_zod.z.object({
1872
+ listenAddress: import_zod.z.string().optional(),
1873
+ address: import_zod.z.string(),
1874
+ 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]).optional()
1875
+ });
1876
+ TunnelConfigV1Schema = import_zod.z.object({
1877
+ // Meta Info
1878
+ version: import_zod.z.string(),
1879
+ name: import_zod.z.string(),
1880
+ configId: import_zod.z.string(),
1881
+ // General tunnel configurations
1882
+ serverAddress: import_zod.z.string().optional(),
1883
+ token: import_zod.z.string().optional(),
1884
+ autoReconnect: import_zod.z.boolean().optional(),
1885
+ reconnectInterval: import_zod.z.number().optional(),
1886
+ maxReconnectAttempts: import_zod.z.number().optional(),
1887
+ force: import_zod.z.boolean(),
1888
+ keepAliveInterval: import_zod.z.number().optional(),
1889
+ webDebugger: import_zod.z.string(),
1890
+ //Forwarding
1891
+ // Either a URL string (e.g. "https://localhost:5555") or an array of forwarding entries.
1892
+ forwarding: import_zod.z.union([
1893
+ import_zod.z.string(),
1894
+ import_zod.z.array(ForwardingEntryV2Schema)
1895
+ ]),
1896
+ // IP whitelist
1897
+ ipWhitelist: import_zod.z.array(import_zod.z.string()).optional(),
1898
+ basicAuth: import_zod.z.array(import_zod.z.object({ username: import_zod.z.string(), password: import_zod.z.string() })).optional(),
1899
+ bearerTokenAuth: import_zod.z.array(import_zod.z.string()).optional(),
1900
+ headerModification: import_zod.z.array(HeaderModificationSchema).optional(),
1901
+ reverseProxy: import_zod.z.boolean().optional(),
1902
+ xForwardedFor: import_zod.z.boolean().optional(),
1903
+ httpsOnly: import_zod.z.boolean().optional(),
1904
+ originalRequestUrl: import_zod.z.boolean().optional(),
1905
+ allowPreflight: import_zod.z.boolean().optional(),
1906
+ serve: import_zod.z.string().optional(),
1907
+ optional: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
1908
+ });
1909
+ StartV2Schema = import_zod.z.object({
1910
+ tunnelID: import_zod.z.string().nullable().optional(),
1911
+ tunnelConfig: TunnelConfigV1Schema
1912
+ });
1913
+ UpdateConfigV2Schema = import_zod.z.object({
1914
+ tunnelConfig: TunnelConfigV1Schema
1915
+ });
1775
1916
  }
1776
1917
  });
1777
1918
 
1778
1919
  // src/remote_management/handler.ts
1779
- var import_pinggy4, TunnelOperations;
1920
+ var TunnelOperations;
1780
1921
  var init_handler = __esm({
1781
1922
  "src/remote_management/handler.ts"() {
1782
1923
  "use strict";
@@ -1784,7 +1925,6 @@ var init_handler = __esm({
1784
1925
  init_types();
1785
1926
  init_TunnelManager();
1786
1927
  init_remote_schema();
1787
- import_pinggy4 = require("@pinggy/pinggy");
1788
1928
  TunnelOperations = class {
1789
1929
  constructor() {
1790
1930
  this.tunnelManager = TunnelManager.getInstance();
@@ -1803,7 +1943,7 @@ var init_handler = __esm({
1803
1943
  return status;
1804
1944
  }
1805
1945
  // --- Helper to construct TunnelResponse ---
1806
- async buildTunnelResponse(tunnelid, tunnelConfig, configid, tunnelName, additionalForwarding, serve) {
1946
+ async buildTunnelResponse(tunnelid, tunnelConfig, configid, tunnelName, serve) {
1807
1947
  const [status, stats, tlsInfo, greetMsg, remoteurls] = await Promise.all([
1808
1948
  this.tunnelManager.getTunnelStatus(tunnelid),
1809
1949
  this.tunnelManager.getLatestTunnelStats(tunnelid) || newStats(),
@@ -1814,11 +1954,27 @@ var init_handler = __esm({
1814
1954
  return {
1815
1955
  tunnelid,
1816
1956
  remoteurls,
1817
- tunnelconfig: pinggyOptionsToTunnelConfig(tunnelConfig, configid, tunnelName, tlsInfo, greetMsg, additionalForwarding),
1957
+ tunnelconfig: pinggyOptionsToTunnelConfig(tunnelConfig, configid, tunnelName, tlsInfo, greetMsg),
1818
1958
  status: this.buildStatus(tunnelid, status, "" /* NoError */),
1819
1959
  stats
1820
1960
  };
1821
1961
  }
1962
+ async buildTunnelResponseV2(tunnelid, tunnelConfig, configFromCli, configid, tunnelName, serve) {
1963
+ const [status, stats, greetMsg, remoteurls] = await Promise.all([
1964
+ this.tunnelManager.getTunnelStatus(tunnelid),
1965
+ this.tunnelManager.getLatestTunnelStats(tunnelid) || newStats(),
1966
+ this.tunnelManager.getTunnelGreetMessage(tunnelid),
1967
+ this.tunnelManager.getTunnelUrls(tunnelid)
1968
+ ]);
1969
+ return {
1970
+ tunnelid,
1971
+ remoteurls,
1972
+ tunnelconfig: pinggyOptionsToTunnelConfigV1(tunnelConfig, configFromCli),
1973
+ status: this.buildStatus(tunnelid, status, "" /* NoError */),
1974
+ stats,
1975
+ greetmsg: greetMsg
1976
+ };
1977
+ }
1822
1978
  error(code, err, fallback) {
1823
1979
  return newErrorResponse({
1824
1980
  code,
@@ -1829,19 +1985,28 @@ var init_handler = __esm({
1829
1985
  async handleStart(config) {
1830
1986
  try {
1831
1987
  const opts = tunnelConfigToPinggyOptions(config);
1832
- const additionalForwardingParsed = config.additionalForwarding || [];
1833
- const { tunnelid, instance, tunnelName, additionalForwarding, serve } = await this.tunnelManager.createTunnel({
1988
+ const { tunnelid, instance, tunnelName, serve, tunnelConfig } = await this.tunnelManager.createTunnel({
1834
1989
  ...opts,
1835
- tunnelType: Array.isArray(config.type) ? config.type : config.type ? [config.type] : [import_pinggy4.TunnelType.Http],
1836
- // Temporary fix in future we will not use this field.
1837
- configid: config.configid,
1838
- tunnelName: config.configname,
1839
- additionalForwarding: additionalForwardingParsed,
1840
- serve: config.serve
1990
+ configId: config.configid,
1991
+ name: config.configname,
1992
+ optional: {
1993
+ serve: config.serve
1994
+ }
1841
1995
  });
1842
- this.tunnelManager.startTunnel(tunnelid);
1996
+ await this.tunnelManager.startTunnel(tunnelid);
1997
+ const tunnelPconfig = await this.tunnelManager.getTunnelConfig("", tunnelid);
1998
+ const resp = this.buildTunnelResponse(tunnelid, tunnelPconfig, config.configid, tunnelName, serve);
1999
+ return resp;
2000
+ } catch (err) {
2001
+ return this.error(ErrorCode.ErrorStartingTunnel, err, "Unknown error occurred while starting tunnel");
2002
+ }
2003
+ }
2004
+ async handleStartV2(config) {
2005
+ try {
2006
+ const { tunnelid, instance, serve } = await this.tunnelManager.createTunnel(config);
2007
+ await this.tunnelManager.startTunnel(tunnelid);
1843
2008
  const tunnelPconfig = await this.tunnelManager.getTunnelConfig("", tunnelid);
1844
- const resp = this.buildTunnelResponse(tunnelid, tunnelPconfig, config.configid, tunnelName, additionalForwarding, serve);
2009
+ const resp = this.buildTunnelResponseV2(tunnelid, tunnelPconfig, config, config.configId, config.name, config.serve);
1845
2010
  return resp;
1846
2011
  } catch (err) {
1847
2012
  return this.error(ErrorCode.ErrorStartingTunnel, err, "Unknown error occurred while starting tunnel");
@@ -1852,20 +2017,59 @@ var init_handler = __esm({
1852
2017
  const opts = tunnelConfigToPinggyOptions(config);
1853
2018
  const tunnel = await this.tunnelManager.updateConfig({
1854
2019
  ...opts,
1855
- tunnelType: Array.isArray(config.type) ? config.type : config.type ? [config.type] : [import_pinggy4.TunnelType.Http],
1856
- // // Temporary fix in future we will not use this field.
1857
- configid: config.configid,
1858
- tunnelName: config.configname,
1859
- additionalForwarding: config.additionalForwarding || [],
1860
- serve: config.serve
2020
+ configId: config.configid,
2021
+ name: config.configname,
2022
+ optional: {
2023
+ serve: config.serve
2024
+ }
1861
2025
  });
1862
2026
  if (!tunnel.instance || !tunnel.tunnelConfig)
1863
2027
  throw new Error("Invalid tunnel state after configuration update");
1864
- return this.buildTunnelResponse(tunnel.tunnelid, tunnel.tunnelConfig, config.configid, tunnel.tunnelName, tunnel.additionalForwarding, tunnel.serve);
2028
+ return this.buildTunnelResponse(tunnel.tunnelid, tunnel.tunnelConfig, config.configid, tunnel.tunnelName, tunnel.serve);
1865
2029
  } catch (err) {
1866
2030
  return this.error(ErrorCode.InternalServerError, err, "Failed to update tunnel configuration");
1867
2031
  }
1868
2032
  }
2033
+ async handleUpdateConfigV2(config) {
2034
+ try {
2035
+ const tunnel = await this.tunnelManager.updateConfig(config);
2036
+ if (!tunnel.instance || !tunnel.tunnelConfig)
2037
+ throw new Error("Invalid tunnel state after configuration update");
2038
+ return this.buildTunnelResponseV2(tunnel.tunnelid, tunnel.tunnelConfig, config, config.configId, tunnel.tunnelName, tunnel.serve);
2039
+ } catch (err) {
2040
+ return this.error(ErrorCode.InternalServerError, err, "Failed to update tunnel configuration");
2041
+ }
2042
+ }
2043
+ async handleListV2() {
2044
+ try {
2045
+ const tunnels = await this.tunnelManager.getAllTunnels();
2046
+ if (tunnels.length === 0) {
2047
+ return [];
2048
+ }
2049
+ return Promise.all(
2050
+ tunnels.map(async (t) => {
2051
+ const rawStats = this.tunnelManager.getLatestTunnelStats(t.tunnelid) || newStats();
2052
+ const [status, tlsInfo, greetMsg] = await Promise.all([
2053
+ this.tunnelManager.getTunnelStatus(t.tunnelid),
2054
+ this.tunnelManager.getLocalserverTlsInfo(t.tunnelid),
2055
+ this.tunnelManager.getTunnelGreetMessage(t.tunnelid)
2056
+ ]);
2057
+ const tunnelConfguration = status !== "closed" /* Closed */ && status !== "exited" /* Exited */ ? await this.tunnelManager.getTunnelConfig("", t.tunnelid) : t.tunnelConfig;
2058
+ const tunnelConfig = pinggyOptionsToTunnelConfigV1(tunnelConfguration, t.tunnelConfig);
2059
+ return {
2060
+ tunnelid: t.tunnelid,
2061
+ remoteurls: t.remoteurls,
2062
+ status: this.buildStatus(t.tunnelid, status, "" /* NoError */),
2063
+ stats: rawStats,
2064
+ tunnelconfig: tunnelConfig,
2065
+ greetmsg: greetMsg
2066
+ };
2067
+ })
2068
+ );
2069
+ } catch (err) {
2070
+ return this.error(ErrorCode.InternalServerError, err, "Failed to list tunnels");
2071
+ }
2072
+ }
1869
2073
  async handleList() {
1870
2074
  try {
1871
2075
  const tunnels = await this.tunnelManager.getAllTunnels();
@@ -1881,7 +2085,7 @@ var init_handler = __esm({
1881
2085
  this.tunnelManager.getTunnelGreetMessage(t.tunnelid)
1882
2086
  ]);
1883
2087
  const pinggyOptions = status !== "closed" /* Closed */ && status !== "exited" /* Exited */ ? await this.tunnelManager.getTunnelConfig("", t.tunnelid) : t.tunnelConfig;
1884
- const tunnelConfig = pinggyOptionsToTunnelConfig(pinggyOptions, t.configid, t.tunnelName, tlsInfo, greetMsg, t.additionalForwarding, t.serve);
2088
+ const tunnelConfig = pinggyOptionsToTunnelConfig(pinggyOptions, t.configId, t.tunnelName, tlsInfo, greetMsg, t.serve);
1885
2089
  return {
1886
2090
  tunnelid: t.tunnelid,
1887
2091
  remoteurls: t.remoteurls,
@@ -1897,10 +2101,10 @@ var init_handler = __esm({
1897
2101
  }
1898
2102
  async handleStop(tunnelid) {
1899
2103
  try {
1900
- const { configid } = this.tunnelManager.stopTunnel(tunnelid);
2104
+ const { configId } = this.tunnelManager.stopTunnel(tunnelid);
1901
2105
  const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
1902
2106
  if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
1903
- return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, configid, managed.tunnelName, managed.additionalForwarding, managed.serve);
2107
+ return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, configId, managed.tunnelName, managed.serve);
1904
2108
  } catch (err) {
1905
2109
  return this.error(ErrorCode.TunnelNotFound, err, "Failed to stop tunnel");
1906
2110
  }
@@ -1909,7 +2113,7 @@ var init_handler = __esm({
1909
2113
  try {
1910
2114
  const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
1911
2115
  if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
1912
- return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, managed.configid, managed.tunnelName, managed.additionalForwarding, managed.serve);
2116
+ return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, managed.configId, managed.tunnelName, managed.serve);
1913
2117
  } catch (err) {
1914
2118
  return this.error(ErrorCode.TunnelNotFound, err, "Failed to get tunnel information");
1915
2119
  }
@@ -1919,7 +2123,7 @@ var init_handler = __esm({
1919
2123
  await this.tunnelManager.restartTunnel(tunnelid);
1920
2124
  const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
1921
2125
  if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
1922
- return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, managed.configid, managed.tunnelName, managed.additionalForwarding, managed.serve);
2126
+ return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, managed.configId, managed.tunnelName, managed.serve);
1923
2127
  } catch (err) {
1924
2128
  return this.error(ErrorCode.TunnelNotFound, err, "Failed to restart tunnel");
1925
2129
  }
@@ -1962,7 +2166,216 @@ var init_handler = __esm({
1962
2166
  }
1963
2167
  });
1964
2168
 
2169
+ // src/remote_management/websocket_printer.ts
2170
+ var import_picocolors3, PENDING_START_TIMEOUT_MS, RemoteManagementWebSocketPrinter, remoteManagementWebSocketPrinter;
2171
+ var init_websocket_printer = __esm({
2172
+ "src/remote_management/websocket_printer.ts"() {
2173
+ "use strict";
2174
+ init_cjs_shims();
2175
+ init_logger();
2176
+ init_TunnelManager();
2177
+ init_types();
2178
+ init_printer();
2179
+ import_picocolors3 = __toESM(require("picocolors"), 1);
2180
+ PENDING_START_TIMEOUT_MS = 5 * 60 * 1e3;
2181
+ RemoteManagementWebSocketPrinter = class {
2182
+ constructor() {
2183
+ this.tunnelManager = TunnelManager.getInstance();
2184
+ this.pendingStarts = /* @__PURE__ */ new Map();
2185
+ }
2186
+ setTunnelHandler(tunnelHandler) {
2187
+ this.tunnelHandler = tunnelHandler;
2188
+ }
2189
+ queueStart(config) {
2190
+ this.cleanupExpiredPendingStarts();
2191
+ const entry = {
2192
+ configId: this.getConfigIdFromRequest(config),
2193
+ configName: this.getConfigNameFromRequest(config),
2194
+ queuedAt: Date.now()
2195
+ };
2196
+ this.latestPendingConfigId = entry.configId;
2197
+ this.pendingStarts.set(entry.configId, entry);
2198
+ printer_default.startSpinner("Starting tunnel with config name: " + entry.configName);
2199
+ }
2200
+ failQueuedStart(config, reason) {
2201
+ const configId = this.getConfigIdFromRequest(config);
2202
+ const pending = this.pendingStarts.get(configId);
2203
+ const configName = pending?.configName || this.getConfigNameFromRequest(config);
2204
+ this.pendingStarts.delete(configId);
2205
+ if (this.latestPendingConfigId === configId) {
2206
+ this.latestPendingConfigId = void 0;
2207
+ printer_default.stopSpinnerFail(`Failed to start tunnel with config name: ${configName}. ${reason}`);
2208
+ }
2209
+ }
2210
+ handleStartResult(config, result) {
2211
+ this.cleanupExpiredPendingStarts();
2212
+ const requestedConfigId = this.getConfigIdFromRequest(config);
2213
+ if (this.latestPendingConfigId && requestedConfigId !== this.latestPendingConfigId) {
2214
+ this.pendingStarts.delete(requestedConfigId);
2215
+ return;
2216
+ }
2217
+ if (isErrorResponse(result)) {
2218
+ this.failQueuedStart(config, result.message);
2219
+ return;
2220
+ }
2221
+ const configId = this.getConfigIdFromTunnel(result);
2222
+ const pending = this.pendingStarts.get(requestedConfigId) || {
2223
+ configId: requestedConfigId,
2224
+ configName: this.getConfigNameFromRequest(config),
2225
+ queuedAt: Date.now()
2226
+ };
2227
+ pending.tunnelId = result.tunnelid;
2228
+ this.pendingStarts.set(requestedConfigId, pending);
2229
+ if (result.remoteurls.length > 0) {
2230
+ this.completePendingStart(pending, result.remoteurls);
2231
+ }
2232
+ }
2233
+ printStopRequested(tunnelId) {
2234
+ const details = this.resolveTunnelDetails(tunnelId);
2235
+ printer_default.startSpinner("Stopping tunnel with config name: " + details.configName);
2236
+ }
2237
+ handleStopResult(tunnelId, result) {
2238
+ const details = this.resolveTunnelDetails(tunnelId, result);
2239
+ if (isErrorResponse(result)) {
2240
+ printer_default.stopSpinnerFail("Failed to stop tunnel with config name: " + details.configName);
2241
+ return;
2242
+ }
2243
+ this.pendingStarts.delete(details.configId);
2244
+ printer_default.stopSpinnerSuccess("Stopped tunnel with config name: " + details.configName);
2245
+ }
2246
+ printRestartRequested(tunnelId) {
2247
+ const details = this.resolveTunnelDetails(tunnelId);
2248
+ printer_default.startSpinner("Restarting tunnel with config name: " + details.configName);
2249
+ }
2250
+ handleRestartResult(tunnelId, result) {
2251
+ const details = this.resolveTunnelDetails(tunnelId, result);
2252
+ if (isErrorResponse(result)) {
2253
+ printer_default.warn(`Failed to restart tunnel with config name: ${details.configName}. ${result.message}`);
2254
+ printer_default.stopSpinnerFail("Failed to restart tunnel with config name: " + details.configName);
2255
+ return;
2256
+ }
2257
+ printer_default.stopSpinnerSuccess("Restarted tunnel with config name: " + details.configName);
2258
+ if (result.remoteurls?.length > 0) {
2259
+ printer_default.info(import_picocolors3.default.cyanBright("Remote URLs:"));
2260
+ (result.remoteurls ?? []).forEach(
2261
+ (url) => printer_default.print(" " + import_picocolors3.default.magentaBright(url))
2262
+ );
2263
+ }
2264
+ }
2265
+ monitorList(result) {
2266
+ this.cleanupExpiredPendingStarts();
2267
+ if (!Array.isArray(result) || this.pendingStarts.size === 0 || !this.latestPendingConfigId) {
2268
+ return;
2269
+ }
2270
+ for (const tunnel of result) {
2271
+ const pending = this.findPendingStart(tunnel);
2272
+ if (!pending) {
2273
+ continue;
2274
+ }
2275
+ if (pending.configId !== this.latestPendingConfigId) {
2276
+ continue;
2277
+ }
2278
+ pending.tunnelId = tunnel.tunnelid;
2279
+ this.pendingStarts.set(pending.configId, pending);
2280
+ if (tunnel.remoteurls.length > 0) {
2281
+ this.completePendingStart(pending, tunnel.remoteurls);
2282
+ continue;
2283
+ }
2284
+ if (tunnel.status.state === "exited" /* Exited */) {
2285
+ const reason = tunnel.status.errormsg || "Tunnel exited before a public URL was assigned";
2286
+ this.pendingStarts.delete(pending.configId);
2287
+ this.latestPendingConfigId = void 0;
2288
+ printer_default.stopSpinnerFail(`Tunnel start did not complete for config name: ${pending.configName}. ${reason}`);
2289
+ }
2290
+ }
2291
+ }
2292
+ completePendingStart(entry, urls) {
2293
+ if (this.latestPendingConfigId && entry.configId !== this.latestPendingConfigId) {
2294
+ this.pendingStarts.delete(entry.configId);
2295
+ return;
2296
+ }
2297
+ this.pendingStarts.delete(entry.configId);
2298
+ this.latestPendingConfigId = void 0;
2299
+ printer_default.stopSpinnerSuccess(`Tunnel started with config name: ${entry.configName}.`);
2300
+ printer_default.info(import_picocolors3.default.cyanBright("Remote URLs:"));
2301
+ (urls ?? []).forEach(
2302
+ (url) => printer_default.print(" " + import_picocolors3.default.magentaBright(url))
2303
+ );
2304
+ }
2305
+ cleanupExpiredPendingStarts() {
2306
+ const now = Date.now();
2307
+ for (const [configId, entry] of this.pendingStarts.entries()) {
2308
+ if (now - entry.queuedAt <= PENDING_START_TIMEOUT_MS) {
2309
+ continue;
2310
+ }
2311
+ this.pendingStarts.delete(configId);
2312
+ printer_default.warn(`Timed out while waiting for tunnel URL for config name: ${entry.configName}`);
2313
+ logger.warn("Pending websocket start entry expired", { configId, tunnelId: entry.tunnelId });
2314
+ }
2315
+ }
2316
+ findPendingStart(tunnel) {
2317
+ const configId = this.getConfigIdFromTunnel(tunnel);
2318
+ const byConfigId = this.pendingStarts.get(configId);
2319
+ if (byConfigId) {
2320
+ return byConfigId;
2321
+ }
2322
+ for (const entry of this.pendingStarts.values()) {
2323
+ if (entry.tunnelId === tunnel.tunnelid) {
2324
+ return entry;
2325
+ }
2326
+ }
2327
+ return void 0;
2328
+ }
2329
+ resolveTunnelDetails(tunnelId, result) {
2330
+ try {
2331
+ const managed = this.tunnelManager.getManagedTunnel(void 0, tunnelId);
2332
+ return {
2333
+ configId: managed.configId,
2334
+ configName: managed.tunnelName || managed.configId || tunnelId
2335
+ };
2336
+ } catch {
2337
+ if (result && !isErrorResponse(result)) {
2338
+ return {
2339
+ configId: this.getConfigIdFromTunnel(result),
2340
+ configName: this.getConfigNameFromTunnel(result)
2341
+ };
2342
+ }
2343
+ return {
2344
+ configId: tunnelId,
2345
+ configName: tunnelId
2346
+ };
2347
+ }
2348
+ }
2349
+ getConfigIdFromRequest(config) {
2350
+ return "configid" in config ? config.configid : config.configId;
2351
+ }
2352
+ getConfigNameFromRequest(config) {
2353
+ return "configname" in config ? config.configname : config.name;
2354
+ }
2355
+ getConfigIdFromTunnel(tunnel) {
2356
+ return "configid" in tunnel.tunnelconfig ? tunnel.tunnelconfig.configid : tunnel.tunnelconfig.configId;
2357
+ }
2358
+ getConfigNameFromTunnel(tunnel) {
2359
+ return "configname" in tunnel.tunnelconfig ? tunnel.tunnelconfig.configname : tunnel.tunnelconfig.name;
2360
+ }
2361
+ };
2362
+ remoteManagementWebSocketPrinter = new RemoteManagementWebSocketPrinter();
2363
+ }
2364
+ });
2365
+
1965
2366
  // src/remote_management/websocket_handlers.ts
2367
+ function sendVersionResponse(ws) {
2368
+ const versionResponse = {
2369
+ cli_version: getVersion()
2370
+ };
2371
+ const payload = {
2372
+ command: "get-version",
2373
+ requestid: "0",
2374
+ response: JSON.stringify(versionResponse),
2375
+ error: false
2376
+ };
2377
+ ws.send(JSON.stringify(payload));
2378
+ }
1966
2379
  function handleConnectionStatusMessage(firstMessage) {
1967
2380
  try {
1968
2381
  const text = typeof firstMessage === "string" ? firstMessage : firstMessage.toString();
@@ -1988,11 +2401,14 @@ var init_websocket_handlers = __esm({
1988
2401
  init_types();
1989
2402
  init_handler();
1990
2403
  init_remote_schema();
2404
+ init_websocket_printer();
1991
2405
  import_zod2 = __toESM(require("zod"), 1);
1992
2406
  init_printer();
2407
+ init_util();
1993
2408
  WebSocketCommandHandler = class {
1994
2409
  constructor() {
1995
2410
  this.tunnelHandler = new TunnelOperations();
2411
+ remoteManagementWebSocketPrinter.setTunnelHandler(this.tunnelHandler);
1996
2412
  }
1997
2413
  safeParse(text) {
1998
2414
  if (!text) return void 0;
@@ -2017,35 +2433,157 @@ var init_websocket_handlers = __esm({
2017
2433
  this.sendResponse(ws, resp);
2018
2434
  }
2019
2435
  async handleStartReq(req, raw) {
2020
- const dc = StartSchema.parse(raw);
2021
- printer_default.info("Starting tunnel with config name: " + dc.tunnelConfig.configname);
2022
- const result = await this.tunnelHandler.handleStart(dc.tunnelConfig);
2023
- return this.wrapResponse(result, req);
2436
+ let queuedConfig;
2437
+ try {
2438
+ const dc = StartSchema.parse(raw);
2439
+ queuedConfig = dc.tunnelConfig;
2440
+ remoteManagementWebSocketPrinter.queueStart(dc.tunnelConfig);
2441
+ const result = await this.tunnelHandler.handleStart(dc.tunnelConfig);
2442
+ remoteManagementWebSocketPrinter.handleStartResult(dc.tunnelConfig, result);
2443
+ return this.wrapResponse(result, req);
2444
+ } catch (e) {
2445
+ if (queuedConfig) {
2446
+ remoteManagementWebSocketPrinter.failQueuedStart(queuedConfig, String(e));
2447
+ }
2448
+ if (e instanceof import_zod2.default.ZodError) {
2449
+ printer_default.warn("Validation failed for start request");
2450
+ return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
2451
+ }
2452
+ printer_default.warn(`Error in handleStartReq error: ${String(e)}`);
2453
+ return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
2454
+ }
2455
+ }
2456
+ async handleStartV2Req(req, raw) {
2457
+ let queuedConfig;
2458
+ try {
2459
+ const dc = StartV2Schema.parse(raw);
2460
+ queuedConfig = dc.tunnelConfig;
2461
+ remoteManagementWebSocketPrinter.queueStart(dc.tunnelConfig);
2462
+ const result = await this.tunnelHandler.handleStartV2(dc.tunnelConfig);
2463
+ remoteManagementWebSocketPrinter.handleStartResult(dc.tunnelConfig, result);
2464
+ return this.wrapResponse(result, req);
2465
+ } catch (e) {
2466
+ if (queuedConfig) {
2467
+ remoteManagementWebSocketPrinter.failQueuedStart(queuedConfig, String(e));
2468
+ }
2469
+ if (e instanceof import_zod2.default.ZodError) {
2470
+ printer_default.warn("Validation failed for start-v2 request");
2471
+ return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
2472
+ }
2473
+ printer_default.warn(`Error in handleStartV2Req error: ${String(e)}`);
2474
+ return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
2475
+ }
2024
2476
  }
2025
2477
  async handleStopReq(req, raw) {
2026
- const dc = StopSchema.parse(raw);
2027
- printer_default.info("Stopping tunnel with ID: " + dc.tunnelID);
2028
- const result = await this.tunnelHandler.handleStop(dc.tunnelID);
2029
- return this.wrapResponse(result, req);
2478
+ try {
2479
+ const dc = StopSchema.parse(raw);
2480
+ remoteManagementWebSocketPrinter.printStopRequested(dc.tunnelID);
2481
+ const result = await this.tunnelHandler.handleStop(dc.tunnelID);
2482
+ remoteManagementWebSocketPrinter.handleStopResult(dc.tunnelID, result);
2483
+ return this.wrapResponse(result, req);
2484
+ } catch (e) {
2485
+ if (e instanceof import_zod2.default.ZodError) {
2486
+ printer_default.warn("Validation failed for stop request");
2487
+ return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
2488
+ }
2489
+ printer_default.warn(`Error in handleStopReq error: ${String(e)}`);
2490
+ return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
2491
+ }
2030
2492
  }
2031
2493
  async handleGetReq(req, raw) {
2032
- const dc = GetSchema.parse(raw);
2033
- const result = await this.tunnelHandler.handleGet(dc.tunnelID);
2034
- return this.wrapResponse(result, req);
2494
+ try {
2495
+ const dc = GetSchema.parse(raw);
2496
+ const result = await this.tunnelHandler.handleGet(dc.tunnelID);
2497
+ return this.wrapResponse(result, req);
2498
+ } catch (e) {
2499
+ if (e instanceof import_zod2.default.ZodError) {
2500
+ printer_default.warn("Validation failed for get request");
2501
+ return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
2502
+ }
2503
+ printer_default.warn(`Error in handleGetReq error: ${String(e)}`);
2504
+ return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
2505
+ }
2035
2506
  }
2036
2507
  async handleRestartReq(req, raw) {
2037
- const dc = RestartSchema.parse(raw);
2038
- const result = await this.tunnelHandler.handleRestart(dc.tunnelID);
2039
- return this.wrapResponse(result, req);
2508
+ try {
2509
+ const dc = RestartSchema.parse(raw);
2510
+ remoteManagementWebSocketPrinter.printRestartRequested(dc.tunnelID);
2511
+ const result = await this.tunnelHandler.handleRestart(dc.tunnelID);
2512
+ remoteManagementWebSocketPrinter.handleRestartResult(dc.tunnelID, result);
2513
+ return this.wrapResponse(result, req);
2514
+ } catch (e) {
2515
+ if (e instanceof import_zod2.default.ZodError) {
2516
+ printer_default.warn("Validation failed for restart request");
2517
+ return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
2518
+ }
2519
+ printer_default.warn(`Error in handleRestartReq error: ${String(e)}`);
2520
+ return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
2521
+ }
2040
2522
  }
2041
2523
  async handleUpdateConfigReq(req, raw) {
2042
- const dc = UpdateConfigSchema.parse(raw);
2043
- const result = await this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig);
2044
- return this.wrapResponse(result, req);
2524
+ try {
2525
+ const dc = UpdateConfigSchema.parse(raw);
2526
+ const result = await this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig);
2527
+ return this.wrapResponse(result, req);
2528
+ } catch (e) {
2529
+ if (e instanceof import_zod2.default.ZodError) {
2530
+ printer_default.warn("Validation failed for updateconfig request");
2531
+ return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
2532
+ }
2533
+ printer_default.warn(`Error in handleUpdateConfigReq error: ${String(e)}`);
2534
+ return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
2535
+ }
2536
+ }
2537
+ async handleUpdateConfigV2Req(req, raw) {
2538
+ try {
2539
+ const dc = UpdateConfigV2Schema.parse(raw);
2540
+ const result = await this.tunnelHandler.handleUpdateConfigV2(dc.tunnelConfig);
2541
+ return this.wrapResponse(result, req);
2542
+ } catch (e) {
2543
+ if (e instanceof import_zod2.default.ZodError) {
2544
+ printer_default.warn("Validation failed for update-config-v2 request");
2545
+ return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
2546
+ }
2547
+ printer_default.warn(`Error in handleUpdateConfigV2Req error: ${String(e)}`);
2548
+ return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
2549
+ }
2045
2550
  }
2046
2551
  async handleListReq(req) {
2047
- const result = await this.tunnelHandler.handleList();
2048
- return this.wrapResponse(result, req);
2552
+ try {
2553
+ const result = await this.tunnelHandler.handleList();
2554
+ remoteManagementWebSocketPrinter.monitorList(result);
2555
+ return this.wrapResponse(result, req);
2556
+ } catch (e) {
2557
+ printer_default.warn(`Error in handleListReq error: ${String(e)}`);
2558
+ return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
2559
+ }
2560
+ }
2561
+ async handleListV2Req(req) {
2562
+ try {
2563
+ const result = await this.tunnelHandler.handleListV2();
2564
+ remoteManagementWebSocketPrinter.monitorList(result);
2565
+ return this.wrapResponse(result, req);
2566
+ } catch (e) {
2567
+ printer_default.warn(`Error in handleListV2Req error: ${String(e)}`);
2568
+ return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
2569
+ }
2570
+ }
2571
+ async handleGetVersionReq(ws, req) {
2572
+ try {
2573
+ const versionResponse = {
2574
+ cli_version: getVersion()
2575
+ };
2576
+ const payload = {
2577
+ command: req.command,
2578
+ requestid: req.requestid,
2579
+ response: JSON.stringify(versionResponse),
2580
+ error: false
2581
+ };
2582
+ ws.send(JSON.stringify(payload));
2583
+ } catch (e) {
2584
+ printer_default.warn(`Error in handleGetVersionReq error: ${String(e)}`);
2585
+ this.sendError(ws, req, String(e));
2586
+ }
2049
2587
  }
2050
2588
  wrapResponse(result, req) {
2051
2589
  if (isErrorResponse(result)) {
@@ -2079,6 +2617,10 @@ var init_websocket_handlers = __esm({
2079
2617
  response = await this.handleStartReq(req, raw);
2080
2618
  break;
2081
2619
  }
2620
+ case "start-v2": {
2621
+ response = await this.handleStartV2Req(req, raw);
2622
+ break;
2623
+ }
2082
2624
  case "stop": {
2083
2625
  response = await this.handleStopReq(req, raw);
2084
2626
  break;
@@ -2095,10 +2637,22 @@ var init_websocket_handlers = __esm({
2095
2637
  response = await this.handleUpdateConfigReq(req, raw);
2096
2638
  break;
2097
2639
  }
2640
+ case "update-config-v2": {
2641
+ response = await this.handleUpdateConfigV2Req(req, raw);
2642
+ break;
2643
+ }
2098
2644
  case "list": {
2099
2645
  response = await this.handleListReq(req);
2100
2646
  break;
2101
2647
  }
2648
+ case "list-v2": {
2649
+ response = await this.handleListV2Req(req);
2650
+ break;
2651
+ }
2652
+ case "get-version": {
2653
+ await this.handleGetVersionReq(ws, req);
2654
+ return;
2655
+ }
2102
2656
  default:
2103
2657
  if (typeof req.command === "string") {
2104
2658
  logger.warn("Unknown command", { command: req.command });
@@ -2145,7 +2699,11 @@ async function parseRemoteManagement(values) {
2145
2699
  if (typeof rmToken === "string" && rmToken.trim().length > 0) {
2146
2700
  const manageHost = values["manage"];
2147
2701
  try {
2148
- await initiateRemoteManagement(rmToken, manageHost);
2702
+ const remoteManagementConfig = {
2703
+ apiKey: rmToken,
2704
+ serverUrl: buildRemoteManagementWsUrl(manageHost)
2705
+ };
2706
+ await initiateRemoteManagement(remoteManagementConfig);
2149
2707
  return { ok: true };
2150
2708
  } catch (e) {
2151
2709
  logger.error("Failed to initiate remote management:", e);
@@ -2153,11 +2711,11 @@ async function parseRemoteManagement(values) {
2153
2711
  }
2154
2712
  }
2155
2713
  }
2156
- async function initiateRemoteManagement(token, manage) {
2157
- if (!token || token.trim().length === 0) {
2714
+ async function initiateRemoteManagement(remoteManagementConfig) {
2715
+ if (!remoteManagementConfig.apiKey || remoteManagementConfig.apiKey.trim().length === 0) {
2158
2716
  throw new Error("Remote management token is required (use --remote-management <TOKEN>)");
2159
2717
  }
2160
- const wsUrl = buildRemoteManagementWsUrl(manage);
2718
+ const wsUrl = remoteManagementConfig.serverUrl;
2161
2719
  const wsHost = extractHostname(wsUrl);
2162
2720
  logger.info("Remote management mode enabled.");
2163
2721
  _stopRequested = false;
@@ -2173,7 +2731,7 @@ async function initiateRemoteManagement(token, manage) {
2173
2731
  logConnecting();
2174
2732
  setRemoteManagementState({ status: RemoteManagementStatus.Connecting, errorMessage: "" });
2175
2733
  try {
2176
- await handleWebSocketConnection(wsUrl, wsHost, token);
2734
+ await handleWebSocketConnection(wsUrl, wsHost, remoteManagementConfig.apiKey);
2177
2735
  } catch (error) {
2178
2736
  logger.warn("Remote management connection error", { error: String(error) });
2179
2737
  }
@@ -2201,6 +2759,7 @@ async function handleWebSocketConnection(wsUrl, wsHost, token) {
2201
2759
  };
2202
2760
  ws.once("open", () => {
2203
2761
  printer_default.success(`Connected to ${wsHost}`);
2762
+ setRemoteManagementState({ status: RemoteManagementStatus.Running, errorMessage: "" });
2204
2763
  heartbeat = setInterval(() => {
2205
2764
  if (ws.readyState === import_ws.default.OPEN) ws.ping();
2206
2765
  }, PING_INTERVAL_MS);
@@ -2211,7 +2770,11 @@ async function handleWebSocketConnection(wsUrl, wsHost, token) {
2211
2770
  if (firstMessage) {
2212
2771
  firstMessage = false;
2213
2772
  const ok = handleConnectionStatusMessage(data);
2214
- if (!ok) ws.close();
2773
+ if (!ok) {
2774
+ ws.close();
2775
+ return;
2776
+ }
2777
+ sendVersionResponse(ws);
2215
2778
  return;
2216
2779
  }
2217
2780
  setRemoteManagementState({ status: RemoteManagementStatus.Running, errorMessage: "" });
@@ -2222,20 +2785,21 @@ async function handleWebSocketConnection(wsUrl, wsHost, token) {
2222
2785
  }
2223
2786
  });
2224
2787
  ws.on("unexpected-response", (_, res) => {
2225
- setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: `HTTP ${res.statusCode}` });
2226
2788
  if (res.statusCode === 401) {
2789
+ setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: `HTTP ${res.statusCode}` });
2227
2790
  printer_default.error("Unauthorized. Please enter a valid token.");
2228
2791
  logger.error("Unauthorized (401) on remote management connect");
2792
+ ws.close();
2229
2793
  } else {
2794
+ logger.warn("Unexpected HTTP response ", { statusCode: res.statusCode });
2230
2795
  printer_default.warn(`Unexpected HTTP ${res.statusCode}. Retrying...`);
2231
- logger.warn("Unexpected HTTP response", { statusCode: res.statusCode });
2796
+ cleanup();
2232
2797
  }
2233
- ws.close();
2234
2798
  });
2235
2799
  ws.on("close", (code, reason) => {
2236
2800
  setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: "" });
2237
2801
  logger.info("WebSocket closed", { code, reason: reason.toString() });
2238
- printer_default.warn(`Disconnected (code: ${code}). Retrying...`);
2802
+ printer_default.warn(`Disconnected (code: ${code}). Retrying in ${RECONNECT_SLEEP_MS / 1e3}s...`);
2239
2803
  cleanup();
2240
2804
  });
2241
2805
  ws.on("error", (err) => {
@@ -2325,6 +2889,7 @@ var init_options = __esm({
2325
2889
  localport: { type: "string", short: "l", description: "Takes input as [protocol:][host:]port. Eg. --localport https://localhost:8000 OR -l 3000" },
2326
2890
  debugger: { type: "string", short: "d", description: "Port for web debugger. Eg. --debugger 4300 OR -d 4300" },
2327
2891
  token: { type: "string", description: "Token for authentication. Eg. --token TOKEN_VALUE" },
2892
+ force: { type: "boolean", short: "f", description: "Forcefully close existing tunnels and establish a new tunnel" },
2328
2893
  // Logging options (CLI overrides env)
2329
2894
  loglevel: { type: "string", description: "Logging level: ERROR, INFO, DEBUG. Overrides PINGGY_LOG_LEVEL environment variable" },
2330
2895
  logfile: { type: "string", description: "Path to log file. Overrides PINGGY_LOG_FILE environment variable" },
@@ -2340,7 +2905,8 @@ var init_options = __esm({
2340
2905
  // Remote Control
2341
2906
  "remote-management": { type: "string", description: "Enable remote management of tunnels with token. Eg. --remote-management API_KEY" },
2342
2907
  manage: { type: "string", description: "Provide a server address to manage tunnels. Eg --manage dashboard.pinggy.io" },
2343
- notui: { type: "boolean", description: "Disable TUI in remote management mode" },
2908
+ noTui: { type: "boolean", description: "Disable TUI in remote management mode" },
2909
+ notui: { type: "boolean", description: "hidden", hidden: true },
2344
2910
  // Misc
2345
2911
  version: { type: "boolean", description: "Print version" },
2346
2912
  // Help
@@ -2421,8 +2987,8 @@ var init_defaults = __esm({
2421
2987
  });
2422
2988
 
2423
2989
  // src/cli/extendedOptions.ts
2424
- function parseExtendedOptions(options, config) {
2425
- if (!options) return;
2990
+ function parseExtendedOptions(options, config, localServerTls) {
2991
+ if (!options) return localServerTls;
2426
2992
  for (const opt of options) {
2427
2993
  const [key, value] = opt.replace(/^"|"$/g, "").split(/:(.+)/).filter(Boolean);
2428
2994
  switch (key) {
@@ -2446,10 +3012,16 @@ function parseExtendedOptions(options, config) {
2446
3012
  case "fullrequesturl":
2447
3013
  config.originalRequestUrl = true;
2448
3014
  break;
2449
- default:
2450
- printer_default.warn(`Unknown extended option "${key}"`);
2451
- logger.warn(`Warning: Unknown extended option "${key}"`);
3015
+ default: {
3016
+ if (value && (value.startsWith("localServerTls") || value.startsWith("localservertls"))) {
3017
+ const parts = value.split(/:(.+)/);
3018
+ localServerTls = parts[1] ? parts[1] : "";
3019
+ } else {
3020
+ printer_default.warn(`Unknown extended option "${value}"`);
3021
+ logger.warn(`Warning: Unknown extended option "${value}"`);
3022
+ }
2452
3023
  break;
3024
+ }
2453
3025
  }
2454
3026
  break;
2455
3027
  case "w":
@@ -2520,6 +3092,7 @@ function parseExtendedOptions(options, config) {
2520
3092
  break;
2521
3093
  }
2522
3094
  }
3095
+ return localServerTls;
2523
3096
  }
2524
3097
  function isValidIpV4Cidr(input) {
2525
3098
  if (input.includes("/")) {
@@ -2570,7 +3143,7 @@ function parseUserAndDomain(str) {
2570
3143
  const [user, domain] = str.split("@", 2);
2571
3144
  if (domainRegex.test(domain)) {
2572
3145
  let processKeyword2 = function(keyword) {
2573
- if ([import_pinggy5.TunnelType.Http, import_pinggy5.TunnelType.Tcp, import_pinggy5.TunnelType.Tls, import_pinggy5.TunnelType.Udp, import_pinggy5.TunnelType.TlsTcp].includes(keyword)) {
3146
+ if ([import_pinggy4.TunnelType.Http, import_pinggy4.TunnelType.Tcp, import_pinggy4.TunnelType.Tls, import_pinggy4.TunnelType.Udp, import_pinggy4.TunnelType.TlsTcp].includes(keyword)) {
2574
3147
  type = keyword;
2575
3148
  } else if (keyword === "force") {
2576
3149
  forceFlag = true;
@@ -2639,9 +3212,9 @@ function parseUsers(positionalArgs, explicitToken) {
2639
3212
  return { token, server, type, forceFlag, qrCode, remaining };
2640
3213
  }
2641
3214
  function parseType(finalConfig, values, inferredType) {
2642
- const t = inferredType || values.type || finalConfig.tunnelType;
2643
- 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) {
2644
- finalConfig.tunnelType = [t];
3215
+ const t = inferredType || values.type;
3216
+ if (t === import_pinggy4.TunnelType.Http || t === import_pinggy4.TunnelType.Tcp || t === import_pinggy4.TunnelType.Tls || t === import_pinggy4.TunnelType.Udp || t === import_pinggy4.TunnelType.TlsTcp) {
3217
+ return t;
2645
3218
  }
2646
3219
  }
2647
3220
  function parseLocalPort(finalConfig, values) {
@@ -2775,15 +3348,14 @@ function parseAdditionalForwarding(forwarding) {
2775
3348
  return new Error("forwarding address incorrect: invalid local port");
2776
3349
  }
2777
3350
  return {
2778
- protocol,
2779
- remoteDomain: remoteDomainRaw,
2780
- remotePort,
2781
- localDomain,
2782
- localPort
3351
+ type: protocol,
3352
+ listenAddress: `${remoteDomainRaw}:${remotePort}`,
3353
+ address: `${localDomain}:${localPort}`
2783
3354
  };
2784
3355
  }
2785
- function parseReverseTunnelAddr(finalConfig, values) {
3356
+ function parseReverseTunnelAddr(finalConfig, values, primaryType) {
2786
3357
  const reverseTunnel = values.R;
3358
+ let forwardingData = [];
2787
3359
  if ((!Array.isArray(reverseTunnel) || reverseTunnel.length === 0) && !values.localport && !finalConfig.forwarding) {
2788
3360
  return new Error("local port not specified. Please use '-h' option for help.");
2789
3361
  }
@@ -2795,18 +3367,21 @@ function parseReverseTunnelAddr(finalConfig, values) {
2795
3367
  if (slicedForwarding.length === 3) {
2796
3368
  const parsed = parseDefaultForwarding(forwarding);
2797
3369
  if (parsed instanceof Error) return parsed;
2798
- finalConfig.forwarding = `${parsed.localDomain}:${parsed.localPort}`;
3370
+ forwardingData.push({
3371
+ address: `${parsed.localDomain}:${parsed.localPort}`,
3372
+ type: primaryType || import_pinggy4.TunnelType.Http
3373
+ });
2799
3374
  } else if (slicedForwarding.length === 4) {
2800
- finalConfig.additionalForwarding ?? (finalConfig.additionalForwarding = []);
2801
3375
  const parsed = parseAdditionalForwarding(forwarding);
2802
3376
  if (parsed instanceof Error) return parsed;
2803
- finalConfig.additionalForwarding.push(parsed);
3377
+ forwardingData.push(parsed);
2804
3378
  } else {
2805
3379
  return new Error(
2806
3380
  "Incorrect command line arguments: reverse tunnel address incorrect. Please use '-h' option for help."
2807
3381
  );
2808
3382
  }
2809
3383
  }
3384
+ finalConfig.forwarding = forwardingData;
2810
3385
  return null;
2811
3386
  }
2812
3387
  function parseLocalTunnelAddr(finalConfig, values) {
@@ -2842,13 +3417,19 @@ function parseToken(finalConfig, explicitToken) {
2842
3417
  }
2843
3418
  }
2844
3419
  function parseArgs(finalConfig, remainingPositionals) {
2845
- parseExtendedOptions(remainingPositionals, finalConfig);
3420
+ let localserverTls = "";
3421
+ localserverTls = parseExtendedOptions(remainingPositionals, finalConfig, localserverTls);
3422
+ if (localserverTls.length > 0 && finalConfig.forwarding) {
3423
+ if (typeof finalConfig.forwarding[0] === "object" && "address" in finalConfig.forwarding[0]) {
3424
+ finalConfig.forwarding[0].address = `https://${finalConfig.forwarding[0].address}`;
3425
+ }
3426
+ }
2846
3427
  }
2847
3428
  function storeJson(config, saveconf) {
2848
3429
  if (saveconf) {
2849
3430
  const path5 = saveconf;
2850
3431
  try {
2851
- import_fs3.default.writeFileSync(path5, JSON.stringify(config, null, 2), { encoding: "utf-8", flag: "w" });
3432
+ import_fs4.default.writeFileSync(path5, JSON.stringify(config, null, 2), { encoding: "utf-8", flag: "w" });
2852
3433
  logger.info(`Configuration saved to ${path5}`);
2853
3434
  } catch (err) {
2854
3435
  const msg = err instanceof Error ? err.message : String(err);
@@ -2859,9 +3440,9 @@ function storeJson(config, saveconf) {
2859
3440
  function loadJsonConfig(config) {
2860
3441
  const configpath = config["conf"];
2861
3442
  if (typeof configpath === "string" && configpath.trim().length > 0) {
2862
- const filepath = import_path3.default.resolve(configpath);
3443
+ const filepath = import_path4.default.resolve(configpath);
2863
3444
  try {
2864
- const data = import_fs3.default.readFileSync(filepath, { encoding: "utf-8" });
3445
+ const data = import_fs4.default.readFileSync(filepath, { encoding: "utf-8" });
2865
3446
  const json = JSON.parse(data);
2866
3447
  return json;
2867
3448
  } catch (err) {
@@ -2880,7 +3461,7 @@ function isSaveConfOption(values) {
2880
3461
  function parseServe(finalConfig, values) {
2881
3462
  const sv = values.serve;
2882
3463
  if (typeof sv !== "string" || sv.trim().length === 0) return null;
2883
- finalConfig.serve = sv;
3464
+ finalConfig.optional.serve = sv;
2884
3465
  return null;
2885
3466
  }
2886
3467
  function parseAutoReconnect(finalConfig, values) {
@@ -2918,21 +3499,23 @@ async function buildFinalConfig(values, positionals) {
2918
3499
  ...defaultOptions,
2919
3500
  ...configFromFile || {},
2920
3501
  // Apply loaded config on top of defaults
2921
- configid: getRandomId(),
3502
+ configId: getRandomId(),
2922
3503
  token: token || (configFromFile?.token || (typeof values.token === "string" ? values.token : "")),
2923
3504
  serverAddress: server || (configFromFile?.serverAddress || defaultOptions.serverAddress),
2924
- tunnelType: initialTunnel ? [initialTunnel] : configFromFile?.tunnelType || [import_pinggy5.TunnelType.Http],
2925
- NoTUI: values.notui || (configFromFile?.NoTUI || false),
2926
- qrCode: qrCode || (configFromFile?.qrCode || false),
2927
- autoReconnect: configFromFile?.autoReconnect ? configFromFile.autoReconnect : defaultOptions.autoReconnect
3505
+ isQRCode: qrCode || (configFromFile?.isQRCode || false),
3506
+ autoReconnect: configFromFile?.autoReconnect ? configFromFile.autoReconnect : defaultOptions.autoReconnect,
3507
+ optional: {
3508
+ serve: configFromFile?.optional?.serve || void 0,
3509
+ noTui: values.noTui || values.notui || (configFromFile?.optional?.noTui || false)
3510
+ }
2928
3511
  };
2929
- parseType(finalConfig, values, type);
3512
+ type = parseType(finalConfig, values, type);
2930
3513
  parseToken(finalConfig, token || values.token);
2931
3514
  const dbgErr = parseDebugger(finalConfig, values);
2932
3515
  if (dbgErr instanceof Error) throw dbgErr;
2933
3516
  const lpErr = parseLocalPort(finalConfig, values);
2934
3517
  if (lpErr instanceof Error) throw lpErr;
2935
- const rErr = parseReverseTunnelAddr(finalConfig, values);
3518
+ const rErr = parseReverseTunnelAddr(finalConfig, values, type);
2936
3519
  if (rErr instanceof Error) throw rErr;
2937
3520
  const lErr = parseLocalTunnelAddr(finalConfig, values);
2938
3521
  if (lErr instanceof Error) throw lErr;
@@ -2940,12 +3523,12 @@ async function buildFinalConfig(values, positionals) {
2940
3523
  if (serveErr instanceof Error) throw serveErr;
2941
3524
  const autoReconnectErr = parseAutoReconnect(finalConfig, values);
2942
3525
  if (autoReconnectErr instanceof Error) throw autoReconnectErr;
2943
- if (forceFlag) finalConfig.force = true;
3526
+ if (forceFlag || values.force) finalConfig.force = true;
2944
3527
  parseArgs(finalConfig, remainingPositionals);
2945
3528
  storeJson(finalConfig, saveconf);
2946
3529
  return finalConfig;
2947
3530
  }
2948
- var import_pinggy5, import_fs3, import_path3, domainRegex, KEYWORDS, VALID_PROTOCOLS;
3531
+ var import_pinggy4, import_fs4, import_path4, domainRegex, KEYWORDS, VALID_PROTOCOLS;
2949
3532
  var init_buildConfig = __esm({
2950
3533
  "src/cli/buildConfig.ts"() {
2951
3534
  "use strict";
@@ -2954,16 +3537,16 @@ var init_buildConfig = __esm({
2954
3537
  init_extendedOptions();
2955
3538
  init_logger();
2956
3539
  init_util();
2957
- import_pinggy5 = require("@pinggy/pinggy");
2958
- import_fs3 = __toESM(require("fs"), 1);
2959
- import_path3 = __toESM(require("path"), 1);
3540
+ import_pinggy4 = require("@pinggy/pinggy");
3541
+ import_fs4 = __toESM(require("fs"), 1);
3542
+ import_path4 = __toESM(require("path"), 1);
2960
3543
  domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
2961
3544
  KEYWORDS = /* @__PURE__ */ new Set([
2962
- import_pinggy5.TunnelType.Http,
2963
- import_pinggy5.TunnelType.Tcp,
2964
- import_pinggy5.TunnelType.Tls,
2965
- import_pinggy5.TunnelType.Udp,
2966
- import_pinggy5.TunnelType.TlsTcp,
3545
+ import_pinggy4.TunnelType.Http,
3546
+ import_pinggy4.TunnelType.Tcp,
3547
+ import_pinggy4.TunnelType.Tls,
3548
+ import_pinggy4.TunnelType.Udp,
3549
+ import_pinggy4.TunnelType.TlsTcp,
2967
3550
  "force",
2968
3551
  "qr"
2969
3552
  ]);
@@ -3000,7 +3583,7 @@ function preprocessWindowsArgs(args) {
3000
3583
  function parseCliArgs(options) {
3001
3584
  const rawArgs = process.argv.slice(2);
3002
3585
  const processedArgs = preprocessWindowsArgs(rawArgs);
3003
- const parsed = (0, import_util4.parseArgs)({
3586
+ const parsed = (0, import_util6.parseArgs)({
3004
3587
  args: processedArgs,
3005
3588
  options,
3006
3589
  allowPositionals: true
@@ -3011,12 +3594,12 @@ function parseCliArgs(options) {
3011
3594
  hasAnyArgs
3012
3595
  };
3013
3596
  }
3014
- var import_util4, os2;
3597
+ var import_util6, os2;
3015
3598
  var init_parseArgs = __esm({
3016
3599
  "src/utils/parseArgs.ts"() {
3017
3600
  "use strict";
3018
3601
  init_cjs_shims();
3019
- import_util4 = require("util");
3602
+ import_util6 = require("util");
3020
3603
  os2 = __toESM(require("os"), 1);
3021
3604
  }
3022
3605
  });
@@ -4448,7 +5031,7 @@ async function launchTui(finalConfig, urls, greet, tunnel) {
4448
5031
  }
4449
5032
  }
4450
5033
  async function startCli(finalConfig, manager) {
4451
- if (!finalConfig.NoTUI && finalConfig.webDebugger === "") {
5034
+ if (!finalConfig.optional?.noTui && finalConfig.webDebugger === "") {
4452
5035
  const freePort = await getFreePort(finalConfig.webDebugger || "");
4453
5036
  finalConfig.webDebugger = `localhost:${freePort}`;
4454
5037
  }
@@ -4456,7 +5039,7 @@ async function startCli(finalConfig, manager) {
4456
5039
  const manager2 = TunnelManager.getInstance();
4457
5040
  const tunnel = await manager2.createTunnel(finalConfig);
4458
5041
  printer_default.startSpinner("Connecting to Pinggy...");
4459
- if (!finalConfig.NoTUI) {
5042
+ if (!finalConfig.optional?.noTui) {
4460
5043
  manager2.registerStatsListener(tunnel.tunnelid, (tunnelId, stats) => {
4461
5044
  globalThis.__PINGGY_TUNNEL_STATS__?.(stats);
4462
5045
  });
@@ -4466,26 +5049,26 @@ async function startCli(finalConfig, manager) {
4466
5049
  });
4467
5050
  await manager2.startTunnel(tunnel.tunnelid);
4468
5051
  printer_default.stopSpinnerSuccess(" Connected to Pinggy");
4469
- printer_default.success(import_picocolors3.default.bold("Tunnel established!"));
4470
- printer_default.print(import_picocolors3.default.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
5052
+ printer_default.success(import_picocolors4.default.bold("Tunnel established!"));
5053
+ printer_default.print(import_picocolors4.default.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
4471
5054
  TunnelData.urls = await manager2.getTunnelUrls(tunnel.tunnelid);
4472
5055
  TunnelData.greet = await manager2.getTunnelGreetMessage(tunnel.tunnelid);
4473
- printer_default.info(import_picocolors3.default.cyanBright("Remote URLs:"));
5056
+ printer_default.info(import_picocolors4.default.cyanBright("Remote URLs:"));
4474
5057
  (TunnelData.urls ?? []).forEach(
4475
- (url) => printer_default.print(" " + import_picocolors3.default.magentaBright(url))
5058
+ (url) => printer_default.print(" " + import_picocolors4.default.magentaBright(url))
4476
5059
  );
4477
- printer_default.print(import_picocolors3.default.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
5060
+ printer_default.print(import_picocolors4.default.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
4478
5061
  if (TunnelData.greet?.includes("not authenticated")) {
4479
- printer_default.warn(import_picocolors3.default.yellowBright(TunnelData.greet));
5062
+ printer_default.warn(import_picocolors4.default.yellowBright(TunnelData.greet));
4480
5063
  } else if (TunnelData.greet?.includes("authenticated as")) {
4481
5064
  const emailMatch = /authenticated as (.+)/.exec(TunnelData.greet);
4482
5065
  if (emailMatch) {
4483
5066
  const email = emailMatch[1];
4484
- printer_default.info(import_picocolors3.default.cyanBright("Authenticated as: " + email));
5067
+ printer_default.info(import_picocolors4.default.cyanBright("Authenticated as: " + email));
4485
5068
  }
4486
5069
  }
4487
- printer_default.print(import_picocolors3.default.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
4488
- printer_default.print(import_picocolors3.default.gray("\nPress Ctrl+C to stop the tunnel.\n"));
5070
+ printer_default.print(import_picocolors4.default.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
5071
+ printer_default.print(import_picocolors4.default.gray("\nPress Ctrl+C to stop the tunnel.\n"));
4489
5072
  manager2.registerWillReconnectListener(tunnel.tunnelid, (tunnelId, error, messages) => {
4490
5073
  if (activeTui) {
4491
5074
  const msg = messages?.join("\n") || error || "Tunnel disconnected, reconnecting...";
@@ -4555,34 +5138,34 @@ async function startCli(finalConfig, manager) {
4555
5138
  printer_default.stopSpinnerSuccess("Reconnected to Pinggy");
4556
5139
  } catch (e) {
4557
5140
  }
4558
- printer_default.success(import_picocolors3.default.bold("Tunnel re-established!"));
4559
- printer_default.print(import_picocolors3.default.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
5141
+ printer_default.success(import_picocolors4.default.bold("Tunnel re-established!"));
5142
+ printer_default.print(import_picocolors4.default.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
4560
5143
  TunnelData.urls = urls;
4561
5144
  TunnelData.greet = await manager2.getTunnelGreetMessage(tunnel.tunnelid);
4562
- printer_default.info(import_picocolors3.default.cyanBright("Remote URLs:"));
5145
+ printer_default.info(import_picocolors4.default.cyanBright("Remote URLs:"));
4563
5146
  (TunnelData.urls ?? []).forEach(
4564
- (url) => printer_default.print(" " + import_picocolors3.default.magentaBright(url))
5147
+ (url) => printer_default.print(" " + import_picocolors4.default.magentaBright(url))
4565
5148
  );
4566
- printer_default.print(import_picocolors3.default.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
5149
+ printer_default.print(import_picocolors4.default.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
4567
5150
  if (TunnelData.greet?.includes("not authenticated")) {
4568
- printer_default.warn(import_picocolors3.default.yellowBright(TunnelData.greet));
5151
+ printer_default.warn(import_picocolors4.default.yellowBright(TunnelData.greet));
4569
5152
  } else if (TunnelData.greet?.includes("authenticated as")) {
4570
5153
  const emailMatch = /authenticated as (.+)/.exec(TunnelData.greet);
4571
5154
  if (emailMatch) {
4572
5155
  const email = emailMatch[1];
4573
- printer_default.info(import_picocolors3.default.cyanBright("Authenticated as: " + email));
5156
+ printer_default.info(import_picocolors4.default.cyanBright("Authenticated as: " + email));
4574
5157
  }
4575
5158
  }
4576
- printer_default.print(import_picocolors3.default.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
4577
- printer_default.print(import_picocolors3.default.gray("\nPress Ctrl+C to stop the tunnel.\n"));
4578
- if (!finalConfig.NoTUI) {
5159
+ printer_default.print(import_picocolors4.default.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
5160
+ printer_default.print(import_picocolors4.default.gray("\nPress Ctrl+C to stop the tunnel.\n"));
5161
+ if (!finalConfig.optional?.noTui) {
4579
5162
  await launchTui(finalConfig, TunnelData.urls, TunnelData.greet, tunnel);
4580
5163
  }
4581
5164
  });
4582
5165
  } catch (e) {
4583
5166
  logger.debug("Failed to register start listener", e);
4584
5167
  }
4585
- if (!finalConfig.NoTUI) {
5168
+ if (!finalConfig.optional?.noTui) {
4586
5169
  await launchTui(finalConfig, TunnelData.urls, TunnelData.greet, tunnel);
4587
5170
  }
4588
5171
  } catch (err) {
@@ -4591,7 +5174,7 @@ async function startCli(finalConfig, manager) {
4591
5174
  throw err;
4592
5175
  }
4593
5176
  }
4594
- var import_picocolors3, TunnelData, activeTui, disconnectState;
5177
+ var import_picocolors4, TunnelData, activeTui, disconnectState;
4595
5178
  var init_starCli = __esm({
4596
5179
  "src/cli/starCli.ts"() {
4597
5180
  "use strict";
@@ -4600,7 +5183,7 @@ var init_starCli = __esm({
4600
5183
  init_TunnelManager();
4601
5184
  init_getFreePort();
4602
5185
  init_logger();
4603
- import_picocolors3 = __toESM(require("picocolors"), 1);
5186
+ import_picocolors4 = __toESM(require("picocolors"), 1);
4604
5187
  init_blessed();
4605
5188
  TunnelData = {
4606
5189
  urls: null,
@@ -4657,7 +5240,7 @@ async function main() {
4657
5240
  printer_default.error(error);
4658
5241
  }
4659
5242
  }
4660
- var import_url, import_process, import_fs4, currentFile, entryFile;
5243
+ var import_url2, import_process, import_fs5, currentFile, entryFile;
4661
5244
  var init_main = __esm({
4662
5245
  "src/main.ts"() {
4663
5246
  "use strict";
@@ -4673,15 +5256,15 @@ var init_main = __esm({
4673
5256
  init_starCli();
4674
5257
  init_util();
4675
5258
  init_handler();
4676
- import_url = require("url");
5259
+ import_url2 = require("url");
4677
5260
  import_process = require("process");
4678
- import_fs4 = require("fs");
5261
+ import_fs5 = require("fs");
4679
5262
  init_logger();
4680
5263
  init_remoteManagement();
4681
- currentFile = (0, import_url.fileURLToPath)(importMetaUrl);
5264
+ currentFile = (0, import_url2.fileURLToPath)(importMetaUrl);
4682
5265
  entryFile = null;
4683
5266
  try {
4684
- entryFile = import_process.argv[1] ? (0, import_fs4.realpathSync)(import_process.argv[1]) : null;
5267
+ entryFile = import_process.argv[1] ? (0, import_fs5.realpathSync)(import_process.argv[1]) : null;
4685
5268
  } catch (e) {
4686
5269
  entryFile = null;
4687
5270
  }