pinggy 0.3.8 → 0.3.10

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
  }
@@ -146,7 +149,7 @@ var init_printer = __esm({
146
149
  message: (err) => {
147
150
  const match = /Unknown option '(.+?)'/.exec(err.message);
148
151
  const option = match ? match[1] : "(unknown)";
149
- return `Unknown option '${option}'. Please check your command or use pinggy --h for guidance.`;
152
+ return `Unknown option '${option}'. Please check your command or use pinggy -h for guidance.`;
150
153
  }
151
154
  },
152
155
  {
@@ -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,25 +395,25 @@ 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,
400
405
  createdAt: now,
401
406
  startedAt: null,
402
407
  stoppedAt: null,
403
- autoReconnect: params.autoReconnect
408
+ autoReconnect: params.autoReconnect,
409
+ lastError: {}
404
410
  };
405
411
  instance.setTunnelEstablishedCallback(({}) => {
406
412
  managed.startedAt = (/* @__PURE__ */ new Date()).toISOString();
407
413
  });
408
414
  this.setupStatsCallback(params.tunnelid, managed);
409
415
  this.setupErrorCallback(params.tunnelid, managed);
416
+ this.setupTunnelPollingErrorCallback(params.tunnelid, managed);
410
417
  this.setupDisconnectCallback(params.tunnelid, managed);
411
418
  this.setupWillReconnectCallback(params.tunnelid, managed);
412
419
  this.setupReconnectingCallback(params.tunnelid, managed);
@@ -414,44 +421,10 @@ var init_TunnelManager = __esm({
414
421
  this.setupReconnectionFailedCallback(params.tunnelid, managed);
415
422
  this.setUpTunnelWorkerErrorCallback(params.tunnelid, managed);
416
423
  this.tunnelsByTunnelId.set(params.tunnelid, managed);
417
- this.tunnelsByConfigId.set(params.configid, managed);
418
- logger.info("Tunnel created", { configid: params.configid, tunnelid: params.tunnelid });
424
+ this.tunnelsByConfigId.set(params.configId, managed);
425
+ logger.info("Tunnel created", { configId: params.configId, tunnelId: params.tunnelid });
419
426
  return managed;
420
427
  }
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
428
  /**
456
429
  * Start a tunnel that was created but not yet started
457
430
  */
@@ -463,7 +436,14 @@ var init_TunnelManager = __esm({
463
436
  try {
464
437
  urls = await managed.instance.start();
465
438
  } catch (error) {
466
- logger.error("Failed to start tunnel", { tunnelId, error });
439
+ logger.warn("Failed to start tunnel", { tunnelId, error });
440
+ managed.isStopped = true;
441
+ managed.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
442
+ managed.lastError = {
443
+ message: "Failed to start tunnel",
444
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
445
+ isFatal: true
446
+ };
467
447
  throw error;
468
448
  }
469
449
  logger.info("Tunnel started", { tunnelId, urls });
@@ -499,7 +479,7 @@ var init_TunnelManager = __esm({
499
479
  stopTunnel(tunnelId) {
500
480
  const managed = this.tunnelsByTunnelId.get(tunnelId);
501
481
  if (!managed) throw new Error(`Tunnel "${tunnelId}" not found`);
502
- logger.info("Stopping tunnel", { tunnelId, configId: managed.configid });
482
+ logger.info("Stopping tunnel", { tunnelId, configId: managed.configId });
503
483
  try {
504
484
  managed.instance.stop();
505
485
  if (managed.serveWorker) {
@@ -511,6 +491,7 @@ var init_TunnelManager = __esm({
511
491
  this.tunnelStats.delete(tunnelId);
512
492
  this.tunnelStatsListeners.delete(tunnelId);
513
493
  this.tunnelErrorListeners.delete(tunnelId);
494
+ this.tunnelPollingErrorListeners.delete(tunnelId);
514
495
  this.tunnelDisconnectListeners.delete(tunnelId);
515
496
  this.tunnelWorkerErrorListeners.delete(tunnelId);
516
497
  this.tunnelStartListeners.delete(tunnelId);
@@ -522,8 +503,8 @@ var init_TunnelManager = __esm({
522
503
  managed.warnings = managed.warnings ?? [];
523
504
  managed.isStopped = true;
524
505
  managed.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
525
- logger.info("Tunnel stopped", { tunnelId, configId: managed.configid });
526
- return { configid: managed.configid, tunnelid: managed.tunnelid };
506
+ logger.info("Tunnel stopped", { tunnelId, configId: managed.configId });
507
+ return { configId: managed.configId, tunnelid: managed.tunnelid };
527
508
  } catch (error) {
528
509
  logger.error("Failed to stop tunnel", { tunnelId, error });
529
510
  throw error;
@@ -540,7 +521,6 @@ var init_TunnelManager = __esm({
540
521
  return [];
541
522
  }
542
523
  const urls = await managed.instance.urls();
543
- logger.debug("Queried tunnel URLs", { tunnelId, urls });
544
524
  return urls;
545
525
  } catch (error) {
546
526
  logger.error("Error fetching tunnel URLs", { tunnelId, error });
@@ -556,11 +536,10 @@ var init_TunnelManager = __esm({
556
536
  const tunnelList = await Promise.all(Array.from(this.tunnelsByTunnelId.values()).map(async (tunnel) => {
557
537
  return {
558
538
  tunnelid: tunnel.tunnelid,
559
- configid: tunnel.configid,
539
+ configId: tunnel.configId,
560
540
  tunnelName: tunnel.tunnelName,
561
541
  tunnelConfig: tunnel.tunnelConfig,
562
- remoteurls: !tunnel.isStopped ? await this.getTunnelUrls(tunnel.tunnelid) : [],
563
- additionalForwarding: tunnel.additionalForwarding,
542
+ remoteurls: tunnel.isStopped || tunnel.lastError?.isFatal ? [] : await this.getTunnelUrls(tunnel.tunnelid),
564
543
  serve: tunnel.serve
565
544
  };
566
545
  }));
@@ -583,7 +562,6 @@ var init_TunnelManager = __esm({
583
562
  return "exited";
584
563
  }
585
564
  const status = await managed.instance.getStatus();
586
- logger.debug("Queried tunnel status", { tunnelId, status });
587
565
  return status;
588
566
  }
589
567
  /**
@@ -601,6 +579,15 @@ var init_TunnelManager = __esm({
601
579
  this.tunnelsByConfigId.clear();
602
580
  this.tunnelStats.clear();
603
581
  this.tunnelStatsListeners.clear();
582
+ this.tunnelErrorListeners.clear();
583
+ this.tunnelPollingErrorListeners.clear();
584
+ this.tunnelDisconnectListeners.clear();
585
+ this.tunnelWorkerErrorListeners.clear();
586
+ this.tunnelStartListeners.clear();
587
+ this.tunnelWillReconnectListeners.clear();
588
+ this.tunnelReconnectingListeners.clear();
589
+ this.tunnelReconnectionCompletedListeners.clear();
590
+ this.tunnelReconnectionFailedListeners.clear();
604
591
  logger.info("All tunnels stopped and cleared");
605
592
  }
606
593
  /**
@@ -621,7 +608,7 @@ var init_TunnelManager = __esm({
621
608
  return false;
622
609
  }
623
610
  this._cleanupTunnelRecords(managed);
624
- logger.info("Removed stopped tunnel records", { tunnelId, configId: managed.configid });
611
+ logger.info("Removed stopped tunnel records", { tunnelId, configId: managed.configId });
625
612
  return true;
626
613
  }
627
614
  /**
@@ -648,6 +635,7 @@ var init_TunnelManager = __esm({
648
635
  this.tunnelStats.delete(managed.tunnelid);
649
636
  this.tunnelStatsListeners.delete(managed.tunnelid);
650
637
  this.tunnelErrorListeners.delete(managed.tunnelid);
638
+ this.tunnelPollingErrorListeners.delete(managed.tunnelid);
651
639
  this.tunnelDisconnectListeners.delete(managed.tunnelid);
652
640
  this.tunnelWorkerErrorListeners.delete(managed.tunnelid);
653
641
  this.tunnelStartListeners.delete(managed.tunnelid);
@@ -656,7 +644,7 @@ var init_TunnelManager = __esm({
656
644
  this.tunnelReconnectionCompletedListeners.delete(managed.tunnelid);
657
645
  this.tunnelReconnectionFailedListeners.delete(managed.tunnelid);
658
646
  this.tunnelsByTunnelId.delete(managed.tunnelid);
659
- this.tunnelsByConfigId.delete(managed.configid);
647
+ this.tunnelsByConfigId.delete(managed.configId);
660
648
  } catch (e) {
661
649
  logger.warn("Failed cleaning up tunnel records", { tunnelId: managed.tunnelid, error: e });
662
650
  }
@@ -717,21 +705,20 @@ var init_TunnelManager = __esm({
717
705
  }
718
706
  logger.info("Initiating tunnel restart", {
719
707
  tunnelId: tunnelid,
720
- configId: existingTunnel.configid
708
+ configId: existingTunnel.configId
721
709
  });
722
710
  try {
723
711
  const tunnelName = existingTunnel.tunnelName;
724
- const currentConfigId = existingTunnel.configid;
712
+ const currentConfigId = existingTunnel.configId;
725
713
  const currentConfig = existingTunnel.tunnelConfig;
726
- const configWithForwarding = existingTunnel.configWithForwarding;
727
- const additionalForwarding = existingTunnel.additionalForwarding;
728
714
  const currentServe = existingTunnel.serve;
729
715
  const autoReconnect = existingTunnel.autoReconnect || false;
730
716
  this.tunnelsByTunnelId.delete(tunnelid);
731
- this.tunnelsByConfigId.delete(existingTunnel.configid);
717
+ this.tunnelsByConfigId.delete(existingTunnel.configId);
732
718
  this.tunnelStats.delete(tunnelid);
733
719
  this.tunnelStatsListeners.delete(tunnelid);
734
720
  this.tunnelErrorListeners.delete(tunnelid);
721
+ this.tunnelPollingErrorListeners.delete(tunnelid);
735
722
  this.tunnelDisconnectListeners.delete(tunnelid);
736
723
  this.tunnelWorkerErrorListeners.delete(tunnelid);
737
724
  this.tunnelStartListeners.delete(tunnelid);
@@ -740,12 +727,10 @@ var init_TunnelManager = __esm({
740
727
  this.tunnelReconnectionCompletedListeners.delete(tunnelid);
741
728
  this.tunnelReconnectionFailedListeners.delete(tunnelid);
742
729
  const newTunnel = await this._createTunnelWithProcessedConfig({
743
- configid: currentConfigId,
730
+ configId: currentConfigId,
744
731
  tunnelid,
745
732
  tunnelName,
746
733
  originalConfig: currentConfig,
747
- configWithForwarding,
748
- additionalForwarding,
749
734
  serve: currentServe,
750
735
  autoReconnect
751
736
  });
@@ -774,20 +759,18 @@ var init_TunnelManager = __esm({
774
759
  * @throws Error if the tunnel is not found or if the update process fails
775
760
  */
776
761
  async updateConfig(newConfig) {
777
- const { configid, tunnelName: newTunnelName, additionalForwarding } = newConfig;
778
- if (!configid || configid.trim().length === 0) {
779
- throw new Error(`Invalid configid: "${configid}"`);
762
+ const { configId, tunnelName: newTunnelName } = newConfig;
763
+ if (!configId || configId.trim().length === 0) {
764
+ throw new Error(`Invalid configId: "${configId}"`);
780
765
  }
781
- const existingTunnel = this.tunnelsByConfigId.get(configid);
766
+ const existingTunnel = this.tunnelsByConfigId.get(configId);
782
767
  if (!existingTunnel) {
783
- throw new Error(`Tunnel with config id "${configid}" not found`);
768
+ throw new Error(`Tunnel with config id "${configId}" not found`);
784
769
  }
785
770
  const isStopped = existingTunnel.isStopped;
786
771
  const currentTunnelConfig = existingTunnel.tunnelConfig;
787
- const currentConfigWithForwarding = existingTunnel.configWithForwarding;
788
772
  const currentTunnelId = existingTunnel.tunnelid;
789
- const currentTunnelConfigId = existingTunnel.configid;
790
- const currentAdditionalForwarding = existingTunnel.additionalForwarding;
773
+ const currentTunnelConfigId = existingTunnel.configId;
791
774
  const currentTunnelName = existingTunnel.tunnelName;
792
775
  const currentServe = existingTunnel.serve;
793
776
  const currentAutoReconnect = existingTunnel.autoReconnect || false;
@@ -799,22 +782,19 @@ var init_TunnelManager = __esm({
799
782
  this.tunnelsByConfigId.delete(currentTunnelConfigId);
800
783
  const mergedBaseConfig = {
801
784
  ...newConfig,
802
- configid,
785
+ configId,
803
786
  tunnelName: newTunnelName !== void 0 ? newTunnelName : currentTunnelName,
804
787
  serve: newConfig.serve !== void 0 ? newConfig.serve : currentServe
805
788
  };
806
- const newConfigWithForwarding = this.buildPinggyConfig(
807
- mergedBaseConfig,
808
- additionalForwarding !== void 0 ? additionalForwarding : currentAdditionalForwarding
809
- );
789
+ const effectiveServe = newConfig.serve !== void 0 ? newConfig.serve : currentServe;
790
+ const effectiveTunnelName = newTunnelName !== void 0 ? newTunnelName : currentTunnelName;
791
+ let configWithForwarding;
810
792
  const newTunnel = await this._createTunnelWithProcessedConfig({
811
- configid,
793
+ configId,
812
794
  tunnelid: currentTunnelId,
813
- tunnelName: newTunnelName !== void 0 ? newTunnelName : currentTunnelName,
795
+ tunnelName: effectiveTunnelName,
814
796
  originalConfig: mergedBaseConfig,
815
- configWithForwarding: newConfigWithForwarding,
816
- additionalForwarding: additionalForwarding !== void 0 ? additionalForwarding : currentAdditionalForwarding,
817
- serve: newConfig.serve !== void 0 ? newConfig.serve : currentServe,
797
+ serve: effectiveServe,
818
798
  autoReconnect: currentAutoReconnect
819
799
  });
820
800
  if (!isStopped) {
@@ -822,23 +802,21 @@ var init_TunnelManager = __esm({
822
802
  }
823
803
  logger.info("Tunnel configuration updated", {
824
804
  tunnelId: newTunnel.tunnelid,
825
- configId: newTunnel.configid,
805
+ configId: newTunnel.configId,
826
806
  isStopped
827
807
  });
828
808
  return newTunnel;
829
809
  } catch (error) {
830
810
  logger.error("Error updating tunnel configuration", {
831
- configId: configid,
811
+ configId,
832
812
  error: error instanceof Error ? error.message : String(error)
833
813
  });
834
814
  try {
835
815
  const originalTunnel = await this._createTunnelWithProcessedConfig({
836
- configid: currentTunnelConfigId,
816
+ configId: currentTunnelConfigId,
837
817
  tunnelid: currentTunnelId,
838
818
  tunnelName: currentTunnelName,
839
819
  originalConfig: currentTunnelConfig,
840
- configWithForwarding: currentConfigWithForwarding,
841
- additionalForwarding: currentAdditionalForwarding,
842
820
  serve: currentServe,
843
821
  autoReconnect: currentAutoReconnect
844
822
  });
@@ -954,6 +932,19 @@ var init_TunnelManager = __esm({
954
932
  logger.info("Error listener registered for tunnel", { tunnelId, listenerId });
955
933
  return listenerId;
956
934
  }
935
+ async registerPollingErrorListener(tunnelId, listener) {
936
+ const managed = this.tunnelsByTunnelId.get(tunnelId);
937
+ if (!managed) {
938
+ throw new Error(`Tunnel "${tunnelId}" not found`);
939
+ }
940
+ if (!this.tunnelPollingErrorListeners.has(tunnelId)) {
941
+ this.tunnelPollingErrorListeners.set(tunnelId, /* @__PURE__ */ new Map());
942
+ }
943
+ const listenerId = getRandomId();
944
+ this.tunnelPollingErrorListeners.get(tunnelId).set(listenerId, listener);
945
+ logger.info("Polling error listener registered for tunnel", { tunnelId, listenerId });
946
+ return listenerId;
947
+ }
957
948
  async registerDisconnectListener(tunnelId, listener) {
958
949
  const managed = this.tunnelsByTunnelId.get(tunnelId);
959
950
  if (!managed) {
@@ -1085,6 +1076,22 @@ var init_TunnelManager = __esm({
1085
1076
  logger.warn("Attempted to deregister non-existent error listener", { tunnelId, listenerId });
1086
1077
  }
1087
1078
  }
1079
+ deregisterPollingErrorListener(tunnelId, listenerId) {
1080
+ const listeners = this.tunnelPollingErrorListeners.get(tunnelId);
1081
+ if (!listeners) {
1082
+ logger.warn("No polling error listeners found for tunnel", { tunnelId });
1083
+ return;
1084
+ }
1085
+ const removed = listeners.delete(listenerId);
1086
+ if (removed) {
1087
+ logger.info("Polling error listener deregistered", { tunnelId, listenerId });
1088
+ if (listeners.size === 0) {
1089
+ this.tunnelPollingErrorListeners.delete(tunnelId);
1090
+ }
1091
+ } else {
1092
+ logger.warn("Attempted to deregister non-existent polling error listener", { tunnelId, listenerId });
1093
+ }
1094
+ }
1088
1095
  deregisterDisconnectListener(tunnelId, listenerId) {
1089
1096
  const listeners = this.tunnelDisconnectListeners.get(tunnelId);
1090
1097
  if (!listeners) {
@@ -1202,6 +1209,46 @@ var init_TunnelManager = __esm({
1202
1209
  logger.warn("Failed to set up stats callback", { tunnelId, error });
1203
1210
  }
1204
1211
  }
1212
+ setupTunnelPollingErrorCallback(tunnelId, managed) {
1213
+ try {
1214
+ const callback = ({ error }) => {
1215
+ try {
1216
+ const errorMessage = error instanceof Error ? error.message : String(error);
1217
+ logger.info("Tunnel reported polling error", { tunnelId, errorMessage });
1218
+ const managedTunnel = this.tunnelsByTunnelId.get(tunnelId);
1219
+ if (managedTunnel) {
1220
+ managedTunnel.lastError = {
1221
+ message: errorMessage,
1222
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1223
+ isFatal: true
1224
+ };
1225
+ }
1226
+ this.notifyPollingErrorListeners(tunnelId, errorMessage);
1227
+ } catch (e) {
1228
+ logger.warn("Error handling tunnel polling error callback", { tunnelId, e });
1229
+ }
1230
+ };
1231
+ managed.instance.setPollingErrorCallback(callback);
1232
+ logger.debug("Tunnel polling error callback set up for tunnel", { tunnelId });
1233
+ } catch (error) {
1234
+ logger.warn("Failed to set up tunnel polling error callback", { tunnelId, error });
1235
+ }
1236
+ }
1237
+ notifyPollingErrorListeners(tunnelId, errorMsg) {
1238
+ try {
1239
+ const listeners = this.tunnelPollingErrorListeners.get(tunnelId);
1240
+ if (!listeners) return;
1241
+ for (const [id, listener] of listeners) {
1242
+ try {
1243
+ listener(tunnelId, errorMsg);
1244
+ } catch (err) {
1245
+ logger.debug("Error in polling-error-listener callback", { listenerId: id, tunnelId, err });
1246
+ }
1247
+ }
1248
+ } catch (err) {
1249
+ logger.debug("Failed to notify polling error listeners", { tunnelId, err });
1250
+ }
1251
+ }
1205
1252
  notifyErrorListeners(tunnelId, errorMsg, isFatal) {
1206
1253
  try {
1207
1254
  const listeners = this.tunnelErrorListeners.get(tunnelId);
@@ -1224,6 +1271,14 @@ var init_TunnelManager = __esm({
1224
1271
  const msg = typeof error === "string" ? error : String(error);
1225
1272
  const isFatal = true;
1226
1273
  logger.debug("Tunnel reported error", { tunnelId, errorNo, errorMsg: msg, recoverable });
1274
+ const managedTunnel = this.tunnelsByTunnelId.get(tunnelId);
1275
+ if (managedTunnel) {
1276
+ managedTunnel.lastError = {
1277
+ message: msg,
1278
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1279
+ isFatal: false
1280
+ };
1281
+ }
1227
1282
  this.notifyErrorListeners(tunnelId, msg, isFatal);
1228
1283
  } catch (e) {
1229
1284
  logger.warn("Error handling tunnel error callback", { tunnelId, e });
@@ -1474,9 +1529,9 @@ var init_TunnelManager = __esm({
1474
1529
  }
1475
1530
  startStaticFileServer(managed) {
1476
1531
  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");
1532
+ const __filename4 = (0, import_node_url.fileURLToPath)(importMetaUrl);
1533
+ const __dirname3 = import_node_path.default.dirname(__filename4);
1534
+ const fileServerWorkerPath = import_node_path.default.join(__dirname3, "workers", "file_serve_worker.cjs");
1480
1535
  const staticServerWorker = new import_node_worker_threads.Worker(fileServerWorkerPath, {
1481
1536
  workerData: {
1482
1537
  dir: managed.serve,
@@ -1628,15 +1683,57 @@ var init_types = __esm({
1628
1683
  });
1629
1684
 
1630
1685
  // src/remote_management/remote_schema.ts
1686
+ function pinggyOptionsToTunnelConfigV1(opts, configStoredInCli) {
1687
+ const parsedTokens = opts.bearerTokenAuth ? Array.isArray(opts.bearerTokenAuth) ? opts.bearerTokenAuth : JSON.parse(opts.bearerTokenAuth) : [];
1688
+ return {
1689
+ version: configStoredInCli.version || "1.0",
1690
+ name: configStoredInCli.name || "",
1691
+ configId: configStoredInCli.configId || "",
1692
+ serverAddress: opts.serverAddress || "a.pinggy.io:443",
1693
+ token: opts.token || "",
1694
+ autoReconnect: opts.autoReconnect ?? true,
1695
+ force: opts.force ?? false,
1696
+ webDebugger: opts.webDebugger || "",
1697
+ forwarding: opts.forwarding ? opts.forwarding : "",
1698
+ ipWhitelist: opts.ipWhitelist ? Array.isArray(opts.ipWhitelist) ? opts.ipWhitelist : JSON.parse(opts.ipWhitelist) : [],
1699
+ basicAuth: opts.basicAuth && Object.keys(opts.basicAuth).length ? opts.basicAuth : void 0,
1700
+ bearerTokenAuth: parsedTokens.length ? parsedTokens : void 0,
1701
+ headerModification: opts.headerModification || [],
1702
+ reverseProxy: opts.reverseProxy ?? false,
1703
+ xForwardedFor: !!opts.xForwardedFor,
1704
+ httpsOnly: opts.httpsOnly ?? false,
1705
+ originalRequestUrl: opts.originalRequestUrl ?? false,
1706
+ allowPreflight: opts.allowPreflight ?? false,
1707
+ optional: opts.optional || {}
1708
+ };
1709
+ }
1631
1710
  function tunnelConfigToPinggyOptions(config) {
1711
+ const forwardingData = [];
1712
+ forwardingData.push({
1713
+ address: `${config.forwardedhost}:${config.localport}`,
1714
+ type: config.type || import_pinggy3.TunnelType.Http
1715
+ // Default to HTTP for the primary forwarding entry
1716
+ });
1717
+ if (config.additionalForwarding && Array.isArray(config.additionalForwarding)) {
1718
+ config.additionalForwarding.forEach((entry) => {
1719
+ if (entry.localDomain && entry.localPort && entry.remoteDomain) {
1720
+ const listenAddress = entry.remotePort && isValidPort(entry.remotePort) ? `${entry.remoteDomain}:${entry.remotePort}` : entry.remoteDomain;
1721
+ forwardingData.push({
1722
+ address: `${entry.localDomain}:${entry.localPort}`,
1723
+ listenAddress,
1724
+ type: import_pinggy3.TunnelType.Http
1725
+ });
1726
+ }
1727
+ });
1728
+ }
1632
1729
  return {
1633
1730
  token: config.token || "",
1634
1731
  serverAddress: config.serveraddress || "free.pinggy.io",
1635
- forwarding: `${config.forwardedhost || "localhost"}:${config.localport}`,
1732
+ forwarding: forwardingData,
1636
1733
  webDebugger: config.webdebuggerport ? `localhost:${config.webdebuggerport}` : "",
1637
1734
  ipWhitelist: config.ipwhitelist || [],
1638
1735
  basicAuth: config.basicauth ? config.basicauth : [],
1639
- bearerTokenAuth: config.bearerauth ? [config.bearerauth] : [],
1736
+ bearerTokenAuth: config.bearerauth || [],
1640
1737
  headerModification: config.headermodification,
1641
1738
  xForwardedFor: !!config.xff,
1642
1739
  httpsOnly: config.httpsOnly,
@@ -1650,18 +1747,38 @@ function tunnelConfigToPinggyOptions(config) {
1650
1747
  }
1651
1748
  };
1652
1749
  }
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;
1750
+ function pinggyOptionsToTunnelConfig(opts, configid, configName, localserverTls, greetMsg, serve) {
1751
+ let primaryEntry;
1752
+ let additionalEntries = [];
1753
+ if (Array.isArray(opts.forwarding)) {
1754
+ primaryEntry = opts.forwarding.find((e) => !e.listenAddress) ?? opts.forwarding[0];
1755
+ additionalEntries = opts.forwarding.filter(
1756
+ (e) => e !== primaryEntry && Boolean(e.listenAddress)
1757
+ );
1758
+ }
1759
+ const forwarding = primaryEntry ? String(primaryEntry.address) : String(opts.forwarding);
1760
+ const [parsedForwardedHost, portStr] = forwarding.split(":");
1761
+ const parsedLocalPort = parseInt(portStr, 10);
1762
+ const tunnelType = primaryEntry?.type ?? import_pinggy3.TunnelType.Http;
1763
+ const additionalForwarding = additionalEntries.map((e) => {
1764
+ const [localDomain, localPortStr] = String(e.address).split(":");
1765
+ const [remoteDomain, remotePortStr] = String(e.listenAddress).split(":");
1766
+ const localPort = parseInt(localPortStr, 10);
1767
+ const remotePort = parseInt(remotePortStr, 10);
1768
+ return {
1769
+ localDomain,
1770
+ localPort: isNaN(localPort) ? 0 : localPort,
1771
+ remoteDomain,
1772
+ remotePort: isNaN(remotePort) ? 0 : remotePort
1773
+ };
1774
+ });
1658
1775
  const parsedTokens = opts.bearerTokenAuth ? Array.isArray(opts.bearerTokenAuth) ? opts.bearerTokenAuth : JSON.parse(opts.bearerTokenAuth) : [];
1659
1776
  return {
1660
1777
  allowPreflight: opts.allowPreflight ?? false,
1661
1778
  allowpreflight: opts.allowPreflight ?? false,
1662
1779
  autoreconnect: opts.autoReconnect ?? false,
1663
1780
  basicauth: opts.basicAuth && Object.keys(opts.basicAuth).length ? opts.basicAuth : null,
1664
- bearerauth: parsedTokens.length ? parsedTokens.join(",") : null,
1781
+ bearerauth: parsedTokens.length ? [parsedTokens.join(",")] : null,
1665
1782
  configid,
1666
1783
  configname: configName,
1667
1784
  greetmsg: greetMsg || "",
@@ -1690,16 +1807,17 @@ function pinggyOptionsToTunnelConfig(opts, configid, configName, localserverTls,
1690
1807
  serve: serve || ""
1691
1808
  };
1692
1809
  }
1693
- var import_pinggy3, import_zod, HeaderModificationSchema, AdditionalForwardingSchema, TunnelConfigSchema, StartSchema, StopSchema, GetSchema, RestartSchema, UpdateConfigSchema;
1810
+ var import_pinggy3, import_zod, HeaderModificationSchema, AdditionalForwardingSchema, TunnelConfigSchema, StartSchema, StopSchema, GetSchema, RestartSchema, UpdateConfigSchema, ForwardingEntryV2Schema, TunnelConfigV1Schema, StartV2Schema, UpdateConfigV2Schema;
1694
1811
  var init_remote_schema = __esm({
1695
1812
  "src/remote_management/remote_schema.ts"() {
1696
1813
  "use strict";
1697
1814
  init_cjs_shims();
1698
1815
  import_pinggy3 = require("@pinggy/pinggy");
1699
1816
  import_zod = require("zod");
1817
+ init_util();
1700
1818
  HeaderModificationSchema = import_zod.z.object({
1701
1819
  key: import_zod.z.string(),
1702
- value: import_zod.z.array(import_zod.z.string()).optional(),
1820
+ value: import_zod.z.array(import_zod.z.string()).nullable().optional(),
1703
1821
  type: import_zod.z.enum(["add", "remove", "update"])
1704
1822
  });
1705
1823
  AdditionalForwardingSchema = import_zod.z.object({
@@ -1715,7 +1833,7 @@ var init_remote_schema = __esm({
1715
1833
  // legacy key
1716
1834
  autoreconnect: import_zod.z.boolean(),
1717
1835
  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(),
1836
+ bearerauth: import_zod.z.array(import_zod.z.string()).nullable(),
1719
1837
  configid: import_zod.z.string(),
1720
1838
  configname: import_zod.z.string(),
1721
1839
  greetmsg: import_zod.z.string().optional(),
@@ -1772,11 +1890,56 @@ var init_remote_schema = __esm({
1772
1890
  UpdateConfigSchema = import_zod.z.object({
1773
1891
  tunnelConfig: TunnelConfigSchema
1774
1892
  });
1893
+ ForwardingEntryV2Schema = import_zod.z.object({
1894
+ listenAddress: import_zod.z.string().optional(),
1895
+ address: import_zod.z.string(),
1896
+ 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()
1897
+ });
1898
+ TunnelConfigV1Schema = import_zod.z.object({
1899
+ // Meta Info
1900
+ version: import_zod.z.string(),
1901
+ name: import_zod.z.string(),
1902
+ configId: import_zod.z.string(),
1903
+ // General tunnel configurations
1904
+ serverAddress: import_zod.z.string().optional(),
1905
+ token: import_zod.z.string().optional(),
1906
+ autoReconnect: import_zod.z.boolean().optional(),
1907
+ reconnectInterval: import_zod.z.number().optional(),
1908
+ maxReconnectAttempts: import_zod.z.number().optional(),
1909
+ force: import_zod.z.boolean(),
1910
+ keepAliveInterval: import_zod.z.number().optional(),
1911
+ webDebugger: import_zod.z.string(),
1912
+ //Forwarding
1913
+ // Either a URL string (e.g. "https://localhost:5555") or an array of forwarding entries.
1914
+ forwarding: import_zod.z.union([
1915
+ import_zod.z.string(),
1916
+ import_zod.z.array(ForwardingEntryV2Schema)
1917
+ ]),
1918
+ // IP whitelist
1919
+ ipWhitelist: import_zod.z.array(import_zod.z.string()).optional(),
1920
+ basicAuth: import_zod.z.array(import_zod.z.object({ username: import_zod.z.string(), password: import_zod.z.string() })).optional(),
1921
+ bearerTokenAuth: import_zod.z.array(import_zod.z.string()).optional(),
1922
+ headerModification: import_zod.z.array(HeaderModificationSchema).optional(),
1923
+ reverseProxy: import_zod.z.boolean().optional(),
1924
+ xForwardedFor: import_zod.z.boolean().optional(),
1925
+ httpsOnly: import_zod.z.boolean().optional(),
1926
+ originalRequestUrl: import_zod.z.boolean().optional(),
1927
+ allowPreflight: import_zod.z.boolean().optional(),
1928
+ serve: import_zod.z.string().optional(),
1929
+ optional: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
1930
+ });
1931
+ StartV2Schema = import_zod.z.object({
1932
+ tunnelID: import_zod.z.string().nullable().optional(),
1933
+ tunnelConfig: TunnelConfigV1Schema
1934
+ });
1935
+ UpdateConfigV2Schema = import_zod.z.object({
1936
+ tunnelConfig: TunnelConfigV1Schema
1937
+ });
1775
1938
  }
1776
1939
  });
1777
1940
 
1778
1941
  // src/remote_management/handler.ts
1779
- var import_pinggy4, TunnelOperations;
1942
+ var TunnelOperations;
1780
1943
  var init_handler = __esm({
1781
1944
  "src/remote_management/handler.ts"() {
1782
1945
  "use strict";
@@ -1784,7 +1947,6 @@ var init_handler = __esm({
1784
1947
  init_types();
1785
1948
  init_TunnelManager();
1786
1949
  init_remote_schema();
1787
- import_pinggy4 = require("@pinggy/pinggy");
1788
1950
  TunnelOperations = class {
1789
1951
  constructor() {
1790
1952
  this.tunnelManager = TunnelManager.getInstance();
@@ -1798,12 +1960,15 @@ var init_handler = __esm({
1798
1960
  status.starttimestamp = managed.startedAt || "";
1799
1961
  status.endtimestamp = managed.stoppedAt || "";
1800
1962
  }
1963
+ if (managed?.lastError) {
1964
+ status.lastError = managed.lastError;
1965
+ }
1801
1966
  } catch (e) {
1802
1967
  }
1803
1968
  return status;
1804
1969
  }
1805
1970
  // --- Helper to construct TunnelResponse ---
1806
- async buildTunnelResponse(tunnelid, tunnelConfig, configid, tunnelName, additionalForwarding, serve) {
1971
+ async buildTunnelResponse(tunnelid, tunnelConfig, configid, tunnelName, serve) {
1807
1972
  const [status, stats, tlsInfo, greetMsg, remoteurls] = await Promise.all([
1808
1973
  this.tunnelManager.getTunnelStatus(tunnelid),
1809
1974
  this.tunnelManager.getLatestTunnelStats(tunnelid) || newStats(),
@@ -1814,11 +1979,27 @@ var init_handler = __esm({
1814
1979
  return {
1815
1980
  tunnelid,
1816
1981
  remoteurls,
1817
- tunnelconfig: pinggyOptionsToTunnelConfig(tunnelConfig, configid, tunnelName, tlsInfo, greetMsg, additionalForwarding),
1982
+ tunnelconfig: pinggyOptionsToTunnelConfig(tunnelConfig, configid, tunnelName, tlsInfo, greetMsg),
1818
1983
  status: this.buildStatus(tunnelid, status, "" /* NoError */),
1819
1984
  stats
1820
1985
  };
1821
1986
  }
1987
+ async buildTunnelResponseV2(tunnelid, tunnelConfig, configFromCli, configid, tunnelName, serve) {
1988
+ const [status, stats, greetMsg, remoteurls] = await Promise.all([
1989
+ this.tunnelManager.getTunnelStatus(tunnelid),
1990
+ this.tunnelManager.getLatestTunnelStats(tunnelid) || newStats(),
1991
+ this.tunnelManager.getTunnelGreetMessage(tunnelid),
1992
+ this.tunnelManager.getTunnelUrls(tunnelid)
1993
+ ]);
1994
+ return {
1995
+ tunnelid,
1996
+ remoteurls,
1997
+ tunnelconfig: pinggyOptionsToTunnelConfigV1(tunnelConfig, configFromCli),
1998
+ status: this.buildStatus(tunnelid, status, "" /* NoError */),
1999
+ stats,
2000
+ greetmsg: greetMsg
2001
+ };
2002
+ }
1822
2003
  error(code, err, fallback) {
1823
2004
  return newErrorResponse({
1824
2005
  code,
@@ -1829,19 +2010,28 @@ var init_handler = __esm({
1829
2010
  async handleStart(config) {
1830
2011
  try {
1831
2012
  const opts = tunnelConfigToPinggyOptions(config);
1832
- const additionalForwardingParsed = config.additionalForwarding || [];
1833
- const { tunnelid, instance, tunnelName, additionalForwarding, serve } = await this.tunnelManager.createTunnel({
2013
+ const { tunnelid, instance, tunnelName, serve, tunnelConfig } = await this.tunnelManager.createTunnel({
1834
2014
  ...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
2015
+ configId: config.configid,
2016
+ name: config.configname,
2017
+ optional: {
2018
+ serve: config.serve
2019
+ }
1841
2020
  });
1842
- this.tunnelManager.startTunnel(tunnelid);
2021
+ await this.tunnelManager.startTunnel(tunnelid);
1843
2022
  const tunnelPconfig = await this.tunnelManager.getTunnelConfig("", tunnelid);
1844
- const resp = this.buildTunnelResponse(tunnelid, tunnelPconfig, config.configid, tunnelName, additionalForwarding, serve);
2023
+ const resp = this.buildTunnelResponse(tunnelid, tunnelPconfig, config.configid, tunnelName, serve);
2024
+ return resp;
2025
+ } catch (err) {
2026
+ return this.error(ErrorCode.ErrorStartingTunnel, err, "Unknown error occurred while starting tunnel");
2027
+ }
2028
+ }
2029
+ async handleStartV2(config) {
2030
+ try {
2031
+ const { tunnelid, instance, serve } = await this.tunnelManager.createTunnel(config);
2032
+ await this.tunnelManager.startTunnel(tunnelid);
2033
+ const tunnelPconfig = await this.tunnelManager.getTunnelConfig("", tunnelid);
2034
+ const resp = this.buildTunnelResponseV2(tunnelid, tunnelPconfig, config, config.configId, config.name, config.serve);
1845
2035
  return resp;
1846
2036
  } catch (err) {
1847
2037
  return this.error(ErrorCode.ErrorStartingTunnel, err, "Unknown error occurred while starting tunnel");
@@ -1852,20 +2042,59 @@ var init_handler = __esm({
1852
2042
  const opts = tunnelConfigToPinggyOptions(config);
1853
2043
  const tunnel = await this.tunnelManager.updateConfig({
1854
2044
  ...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
2045
+ configId: config.configid,
2046
+ name: config.configname,
2047
+ optional: {
2048
+ serve: config.serve
2049
+ }
1861
2050
  });
1862
2051
  if (!tunnel.instance || !tunnel.tunnelConfig)
1863
2052
  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);
2053
+ return this.buildTunnelResponse(tunnel.tunnelid, tunnel.tunnelConfig, config.configid, tunnel.tunnelName, tunnel.serve);
2054
+ } catch (err) {
2055
+ return this.error(ErrorCode.InternalServerError, err, "Failed to update tunnel configuration");
2056
+ }
2057
+ }
2058
+ async handleUpdateConfigV2(config) {
2059
+ try {
2060
+ const tunnel = await this.tunnelManager.updateConfig(config);
2061
+ if (!tunnel.instance || !tunnel.tunnelConfig)
2062
+ throw new Error("Invalid tunnel state after configuration update");
2063
+ return this.buildTunnelResponseV2(tunnel.tunnelid, tunnel.tunnelConfig, config, config.configId, tunnel.tunnelName, tunnel.serve);
1865
2064
  } catch (err) {
1866
2065
  return this.error(ErrorCode.InternalServerError, err, "Failed to update tunnel configuration");
1867
2066
  }
1868
2067
  }
2068
+ async handleListV2() {
2069
+ try {
2070
+ const tunnels = await this.tunnelManager.getAllTunnels();
2071
+ if (tunnels.length === 0) {
2072
+ return [];
2073
+ }
2074
+ return Promise.all(
2075
+ tunnels.map(async (t) => {
2076
+ const rawStats = this.tunnelManager.getLatestTunnelStats(t.tunnelid) || newStats();
2077
+ const [status, tlsInfo, greetMsg] = await Promise.all([
2078
+ this.tunnelManager.getTunnelStatus(t.tunnelid),
2079
+ this.tunnelManager.getLocalserverTlsInfo(t.tunnelid),
2080
+ this.tunnelManager.getTunnelGreetMessage(t.tunnelid)
2081
+ ]);
2082
+ const tunnelConfguration = status !== "closed" /* Closed */ && status !== "exited" /* Exited */ ? await this.tunnelManager.getTunnelConfig("", t.tunnelid) : t.tunnelConfig;
2083
+ const tunnelConfig = pinggyOptionsToTunnelConfigV1(tunnelConfguration, t.tunnelConfig);
2084
+ return {
2085
+ tunnelid: t.tunnelid,
2086
+ remoteurls: t.remoteurls,
2087
+ status: this.buildStatus(t.tunnelid, status, "" /* NoError */),
2088
+ stats: rawStats,
2089
+ tunnelconfig: tunnelConfig,
2090
+ greetmsg: greetMsg
2091
+ };
2092
+ })
2093
+ );
2094
+ } catch (err) {
2095
+ return this.error(ErrorCode.InternalServerError, err, "Failed to list tunnels");
2096
+ }
2097
+ }
1869
2098
  async handleList() {
1870
2099
  try {
1871
2100
  const tunnels = await this.tunnelManager.getAllTunnels();
@@ -1881,7 +2110,7 @@ var init_handler = __esm({
1881
2110
  this.tunnelManager.getTunnelGreetMessage(t.tunnelid)
1882
2111
  ]);
1883
2112
  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);
2113
+ const tunnelConfig = pinggyOptionsToTunnelConfig(pinggyOptions, t.configId, t.tunnelName, tlsInfo, greetMsg, t.serve);
1885
2114
  return {
1886
2115
  tunnelid: t.tunnelid,
1887
2116
  remoteurls: t.remoteurls,
@@ -1897,10 +2126,10 @@ var init_handler = __esm({
1897
2126
  }
1898
2127
  async handleStop(tunnelid) {
1899
2128
  try {
1900
- const { configid } = this.tunnelManager.stopTunnel(tunnelid);
2129
+ const { configId } = this.tunnelManager.stopTunnel(tunnelid);
1901
2130
  const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
1902
2131
  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);
2132
+ return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, configId, managed.tunnelName, managed.serve);
1904
2133
  } catch (err) {
1905
2134
  return this.error(ErrorCode.TunnelNotFound, err, "Failed to stop tunnel");
1906
2135
  }
@@ -1909,7 +2138,7 @@ var init_handler = __esm({
1909
2138
  try {
1910
2139
  const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
1911
2140
  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);
2141
+ return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, managed.configId, managed.tunnelName, managed.serve);
1913
2142
  } catch (err) {
1914
2143
  return this.error(ErrorCode.TunnelNotFound, err, "Failed to get tunnel information");
1915
2144
  }
@@ -1919,7 +2148,7 @@ var init_handler = __esm({
1919
2148
  await this.tunnelManager.restartTunnel(tunnelid);
1920
2149
  const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
1921
2150
  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);
2151
+ return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, managed.configId, managed.tunnelName, managed.serve);
1923
2152
  } catch (err) {
1924
2153
  return this.error(ErrorCode.TunnelNotFound, err, "Failed to restart tunnel");
1925
2154
  }
@@ -1962,7 +2191,216 @@ var init_handler = __esm({
1962
2191
  }
1963
2192
  });
1964
2193
 
2194
+ // src/remote_management/websocket_printer.ts
2195
+ var import_picocolors3, PENDING_START_TIMEOUT_MS, RemoteManagementWebSocketPrinter, remoteManagementWebSocketPrinter;
2196
+ var init_websocket_printer = __esm({
2197
+ "src/remote_management/websocket_printer.ts"() {
2198
+ "use strict";
2199
+ init_cjs_shims();
2200
+ init_logger();
2201
+ init_TunnelManager();
2202
+ init_types();
2203
+ init_printer();
2204
+ import_picocolors3 = __toESM(require("picocolors"), 1);
2205
+ PENDING_START_TIMEOUT_MS = 5 * 60 * 1e3;
2206
+ RemoteManagementWebSocketPrinter = class {
2207
+ constructor() {
2208
+ this.tunnelManager = TunnelManager.getInstance();
2209
+ this.pendingStarts = /* @__PURE__ */ new Map();
2210
+ }
2211
+ setTunnelHandler(tunnelHandler) {
2212
+ this.tunnelHandler = tunnelHandler;
2213
+ }
2214
+ queueStart(config) {
2215
+ this.cleanupExpiredPendingStarts();
2216
+ const entry = {
2217
+ configId: this.getConfigIdFromRequest(config),
2218
+ configName: this.getConfigNameFromRequest(config),
2219
+ queuedAt: Date.now()
2220
+ };
2221
+ this.latestPendingConfigId = entry.configId;
2222
+ this.pendingStarts.set(entry.configId, entry);
2223
+ printer_default.startSpinner("Starting tunnel with config name: " + entry.configName);
2224
+ }
2225
+ failQueuedStart(config, reason) {
2226
+ const configId = this.getConfigIdFromRequest(config);
2227
+ const pending = this.pendingStarts.get(configId);
2228
+ const configName = pending?.configName || this.getConfigNameFromRequest(config);
2229
+ this.pendingStarts.delete(configId);
2230
+ if (this.latestPendingConfigId === configId) {
2231
+ this.latestPendingConfigId = void 0;
2232
+ printer_default.stopSpinnerFail(`Failed to start tunnel with config name: ${configName}. ${reason}`);
2233
+ }
2234
+ }
2235
+ handleStartResult(config, result) {
2236
+ this.cleanupExpiredPendingStarts();
2237
+ const requestedConfigId = this.getConfigIdFromRequest(config);
2238
+ if (this.latestPendingConfigId && requestedConfigId !== this.latestPendingConfigId) {
2239
+ this.pendingStarts.delete(requestedConfigId);
2240
+ return;
2241
+ }
2242
+ if (isErrorResponse(result)) {
2243
+ this.failQueuedStart(config, result.message);
2244
+ return;
2245
+ }
2246
+ const configId = this.getConfigIdFromTunnel(result);
2247
+ const pending = this.pendingStarts.get(requestedConfigId) || {
2248
+ configId: requestedConfigId,
2249
+ configName: this.getConfigNameFromRequest(config),
2250
+ queuedAt: Date.now()
2251
+ };
2252
+ pending.tunnelId = result.tunnelid;
2253
+ this.pendingStarts.set(requestedConfigId, pending);
2254
+ if (result.remoteurls.length > 0) {
2255
+ this.completePendingStart(pending, result.remoteurls);
2256
+ }
2257
+ }
2258
+ printStopRequested(tunnelId) {
2259
+ const details = this.resolveTunnelDetails(tunnelId);
2260
+ printer_default.startSpinner("Stopping tunnel with config name: " + details.configName);
2261
+ }
2262
+ handleStopResult(tunnelId, result) {
2263
+ const details = this.resolveTunnelDetails(tunnelId, result);
2264
+ if (isErrorResponse(result)) {
2265
+ printer_default.stopSpinnerFail("Failed to stop tunnel with config name: " + details.configName);
2266
+ return;
2267
+ }
2268
+ this.pendingStarts.delete(details.configId);
2269
+ printer_default.stopSpinnerSuccess("Stopped tunnel with config name: " + details.configName);
2270
+ }
2271
+ printRestartRequested(tunnelId) {
2272
+ const details = this.resolveTunnelDetails(tunnelId);
2273
+ printer_default.startSpinner("Restarting tunnel with config name: " + details.configName);
2274
+ }
2275
+ handleRestartResult(tunnelId, result) {
2276
+ const details = this.resolveTunnelDetails(tunnelId, result);
2277
+ if (isErrorResponse(result)) {
2278
+ printer_default.warn(`Failed to restart tunnel with config name: ${details.configName}. ${result.message}`);
2279
+ printer_default.stopSpinnerFail("Failed to restart tunnel with config name: " + details.configName);
2280
+ return;
2281
+ }
2282
+ printer_default.stopSpinnerSuccess("Restarted tunnel with config name: " + details.configName);
2283
+ if (result.remoteurls?.length > 0) {
2284
+ printer_default.info(import_picocolors3.default.cyanBright("Remote URLs:"));
2285
+ (result.remoteurls ?? []).forEach(
2286
+ (url) => printer_default.print(" " + import_picocolors3.default.magentaBright(url))
2287
+ );
2288
+ }
2289
+ }
2290
+ monitorList(result) {
2291
+ this.cleanupExpiredPendingStarts();
2292
+ if (!Array.isArray(result) || this.pendingStarts.size === 0 || !this.latestPendingConfigId) {
2293
+ return;
2294
+ }
2295
+ for (const tunnel of result) {
2296
+ const pending = this.findPendingStart(tunnel);
2297
+ if (!pending) {
2298
+ continue;
2299
+ }
2300
+ if (pending.configId !== this.latestPendingConfigId) {
2301
+ continue;
2302
+ }
2303
+ pending.tunnelId = tunnel.tunnelid;
2304
+ this.pendingStarts.set(pending.configId, pending);
2305
+ if (tunnel.remoteurls.length > 0) {
2306
+ this.completePendingStart(pending, tunnel.remoteurls);
2307
+ continue;
2308
+ }
2309
+ if (tunnel.status.state === "exited" /* Exited */) {
2310
+ const reason = tunnel.status.errormsg || "Tunnel exited before a public URL was assigned";
2311
+ this.pendingStarts.delete(pending.configId);
2312
+ this.latestPendingConfigId = void 0;
2313
+ printer_default.stopSpinnerFail(`Tunnel start did not complete for config name: ${pending.configName}. ${reason}`);
2314
+ }
2315
+ }
2316
+ }
2317
+ completePendingStart(entry, urls) {
2318
+ if (this.latestPendingConfigId && entry.configId !== this.latestPendingConfigId) {
2319
+ this.pendingStarts.delete(entry.configId);
2320
+ return;
2321
+ }
2322
+ this.pendingStarts.delete(entry.configId);
2323
+ this.latestPendingConfigId = void 0;
2324
+ printer_default.stopSpinnerSuccess(`Tunnel started with config name: ${entry.configName}.`);
2325
+ printer_default.info(import_picocolors3.default.cyanBright("Remote URLs:"));
2326
+ (urls ?? []).forEach(
2327
+ (url) => printer_default.print(" " + import_picocolors3.default.magentaBright(url))
2328
+ );
2329
+ }
2330
+ cleanupExpiredPendingStarts() {
2331
+ const now = Date.now();
2332
+ for (const [configId, entry] of this.pendingStarts.entries()) {
2333
+ if (now - entry.queuedAt <= PENDING_START_TIMEOUT_MS) {
2334
+ continue;
2335
+ }
2336
+ this.pendingStarts.delete(configId);
2337
+ printer_default.warn(`Timed out while waiting for tunnel URL for config name: ${entry.configName}`);
2338
+ logger.warn("Pending websocket start entry expired", { configId, tunnelId: entry.tunnelId });
2339
+ }
2340
+ }
2341
+ findPendingStart(tunnel) {
2342
+ const configId = this.getConfigIdFromTunnel(tunnel);
2343
+ const byConfigId = this.pendingStarts.get(configId);
2344
+ if (byConfigId) {
2345
+ return byConfigId;
2346
+ }
2347
+ for (const entry of this.pendingStarts.values()) {
2348
+ if (entry.tunnelId === tunnel.tunnelid) {
2349
+ return entry;
2350
+ }
2351
+ }
2352
+ return void 0;
2353
+ }
2354
+ resolveTunnelDetails(tunnelId, result) {
2355
+ try {
2356
+ const managed = this.tunnelManager.getManagedTunnel(void 0, tunnelId);
2357
+ return {
2358
+ configId: managed.configId,
2359
+ configName: managed.tunnelName || managed.configId || tunnelId
2360
+ };
2361
+ } catch {
2362
+ if (result && !isErrorResponse(result)) {
2363
+ return {
2364
+ configId: this.getConfigIdFromTunnel(result),
2365
+ configName: this.getConfigNameFromTunnel(result)
2366
+ };
2367
+ }
2368
+ return {
2369
+ configId: tunnelId,
2370
+ configName: tunnelId
2371
+ };
2372
+ }
2373
+ }
2374
+ getConfigIdFromRequest(config) {
2375
+ return "configid" in config ? config.configid : config.configId;
2376
+ }
2377
+ getConfigNameFromRequest(config) {
2378
+ return "configname" in config ? config.configname : config.name;
2379
+ }
2380
+ getConfigIdFromTunnel(tunnel) {
2381
+ return "configid" in tunnel.tunnelconfig ? tunnel.tunnelconfig.configid : tunnel.tunnelconfig.configId;
2382
+ }
2383
+ getConfigNameFromTunnel(tunnel) {
2384
+ return "configname" in tunnel.tunnelconfig ? tunnel.tunnelconfig.configname : tunnel.tunnelconfig.name;
2385
+ }
2386
+ };
2387
+ remoteManagementWebSocketPrinter = new RemoteManagementWebSocketPrinter();
2388
+ }
2389
+ });
2390
+
1965
2391
  // src/remote_management/websocket_handlers.ts
2392
+ function sendVersionResponse(ws) {
2393
+ const versionResponse = {
2394
+ cli_version: getVersion()
2395
+ };
2396
+ const payload = {
2397
+ command: "get-version",
2398
+ requestid: "0",
2399
+ response: JSON.stringify(versionResponse),
2400
+ error: false
2401
+ };
2402
+ ws.send(JSON.stringify(payload));
2403
+ }
1966
2404
  function handleConnectionStatusMessage(firstMessage) {
1967
2405
  try {
1968
2406
  const text = typeof firstMessage === "string" ? firstMessage : firstMessage.toString();
@@ -1988,11 +2426,14 @@ var init_websocket_handlers = __esm({
1988
2426
  init_types();
1989
2427
  init_handler();
1990
2428
  init_remote_schema();
2429
+ init_websocket_printer();
1991
2430
  import_zod2 = __toESM(require("zod"), 1);
1992
2431
  init_printer();
2432
+ init_util();
1993
2433
  WebSocketCommandHandler = class {
1994
2434
  constructor() {
1995
2435
  this.tunnelHandler = new TunnelOperations();
2436
+ remoteManagementWebSocketPrinter.setTunnelHandler(this.tunnelHandler);
1996
2437
  }
1997
2438
  safeParse(text) {
1998
2439
  if (!text) return void 0;
@@ -2017,35 +2458,157 @@ var init_websocket_handlers = __esm({
2017
2458
  this.sendResponse(ws, resp);
2018
2459
  }
2019
2460
  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);
2461
+ let queuedConfig;
2462
+ try {
2463
+ const dc = StartSchema.parse(raw);
2464
+ queuedConfig = dc.tunnelConfig;
2465
+ remoteManagementWebSocketPrinter.queueStart(dc.tunnelConfig);
2466
+ const result = await this.tunnelHandler.handleStart(dc.tunnelConfig);
2467
+ remoteManagementWebSocketPrinter.handleStartResult(dc.tunnelConfig, result);
2468
+ return this.wrapResponse(result, req);
2469
+ } catch (e) {
2470
+ if (queuedConfig) {
2471
+ remoteManagementWebSocketPrinter.failQueuedStart(queuedConfig, String(e));
2472
+ }
2473
+ if (e instanceof import_zod2.default.ZodError) {
2474
+ printer_default.warn("Validation failed for start request");
2475
+ return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
2476
+ }
2477
+ printer_default.warn(`Error in handleStartReq error: ${String(e)}`);
2478
+ return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
2479
+ }
2480
+ }
2481
+ async handleStartV2Req(req, raw) {
2482
+ let queuedConfig;
2483
+ try {
2484
+ const dc = StartV2Schema.parse(raw);
2485
+ queuedConfig = dc.tunnelConfig;
2486
+ remoteManagementWebSocketPrinter.queueStart(dc.tunnelConfig);
2487
+ const result = await this.tunnelHandler.handleStartV2(dc.tunnelConfig);
2488
+ remoteManagementWebSocketPrinter.handleStartResult(dc.tunnelConfig, result);
2489
+ return this.wrapResponse(result, req);
2490
+ } catch (e) {
2491
+ if (queuedConfig) {
2492
+ remoteManagementWebSocketPrinter.failQueuedStart(queuedConfig, String(e));
2493
+ }
2494
+ if (e instanceof import_zod2.default.ZodError) {
2495
+ printer_default.warn("Validation failed for start-v2 request");
2496
+ return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
2497
+ }
2498
+ printer_default.warn(`Error in handleStartV2Req error: ${String(e)}`);
2499
+ return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
2500
+ }
2024
2501
  }
2025
2502
  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);
2503
+ try {
2504
+ const dc = StopSchema.parse(raw);
2505
+ remoteManagementWebSocketPrinter.printStopRequested(dc.tunnelID);
2506
+ const result = await this.tunnelHandler.handleStop(dc.tunnelID);
2507
+ remoteManagementWebSocketPrinter.handleStopResult(dc.tunnelID, result);
2508
+ return this.wrapResponse(result, req);
2509
+ } catch (e) {
2510
+ if (e instanceof import_zod2.default.ZodError) {
2511
+ printer_default.warn("Validation failed for stop request");
2512
+ return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
2513
+ }
2514
+ printer_default.warn(`Error in handleStopReq error: ${String(e)}`);
2515
+ return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
2516
+ }
2030
2517
  }
2031
2518
  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);
2519
+ try {
2520
+ const dc = GetSchema.parse(raw);
2521
+ const result = await this.tunnelHandler.handleGet(dc.tunnelID);
2522
+ return this.wrapResponse(result, req);
2523
+ } catch (e) {
2524
+ if (e instanceof import_zod2.default.ZodError) {
2525
+ printer_default.warn("Validation failed for get request");
2526
+ return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
2527
+ }
2528
+ printer_default.warn(`Error in handleGetReq error: ${String(e)}`);
2529
+ return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
2530
+ }
2035
2531
  }
2036
2532
  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);
2533
+ try {
2534
+ const dc = RestartSchema.parse(raw);
2535
+ remoteManagementWebSocketPrinter.printRestartRequested(dc.tunnelID);
2536
+ const result = await this.tunnelHandler.handleRestart(dc.tunnelID);
2537
+ remoteManagementWebSocketPrinter.handleRestartResult(dc.tunnelID, result);
2538
+ return this.wrapResponse(result, req);
2539
+ } catch (e) {
2540
+ if (e instanceof import_zod2.default.ZodError) {
2541
+ printer_default.warn("Validation failed for restart request");
2542
+ return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
2543
+ }
2544
+ printer_default.warn(`Error in handleRestartReq error: ${String(e)}`);
2545
+ return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
2546
+ }
2040
2547
  }
2041
2548
  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);
2549
+ try {
2550
+ const dc = UpdateConfigSchema.parse(raw);
2551
+ const result = await this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig);
2552
+ return this.wrapResponse(result, req);
2553
+ } catch (e) {
2554
+ if (e instanceof import_zod2.default.ZodError) {
2555
+ printer_default.warn("Validation failed for updateconfig request");
2556
+ return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
2557
+ }
2558
+ printer_default.warn(`Error in handleUpdateConfigReq error: ${String(e)}`);
2559
+ return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
2560
+ }
2561
+ }
2562
+ async handleUpdateConfigV2Req(req, raw) {
2563
+ try {
2564
+ const dc = UpdateConfigV2Schema.parse(raw);
2565
+ const result = await this.tunnelHandler.handleUpdateConfigV2(dc.tunnelConfig);
2566
+ return this.wrapResponse(result, req);
2567
+ } catch (e) {
2568
+ if (e instanceof import_zod2.default.ZodError) {
2569
+ printer_default.warn("Validation failed for update-config-v2 request");
2570
+ return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
2571
+ }
2572
+ printer_default.warn(`Error in handleUpdateConfigV2Req error: ${String(e)}`);
2573
+ return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
2574
+ }
2045
2575
  }
2046
2576
  async handleListReq(req) {
2047
- const result = await this.tunnelHandler.handleList();
2048
- return this.wrapResponse(result, req);
2577
+ try {
2578
+ const result = await this.tunnelHandler.handleList();
2579
+ remoteManagementWebSocketPrinter.monitorList(result);
2580
+ return this.wrapResponse(result, req);
2581
+ } catch (e) {
2582
+ printer_default.warn(`Error in handleListReq error: ${String(e)}`);
2583
+ return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
2584
+ }
2585
+ }
2586
+ async handleListV2Req(req) {
2587
+ try {
2588
+ const result = await this.tunnelHandler.handleListV2();
2589
+ remoteManagementWebSocketPrinter.monitorList(result);
2590
+ return this.wrapResponse(result, req);
2591
+ } catch (e) {
2592
+ printer_default.warn(`Error in handleListV2Req error: ${String(e)}`);
2593
+ return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
2594
+ }
2595
+ }
2596
+ async handleGetVersionReq(ws, req) {
2597
+ try {
2598
+ const versionResponse = {
2599
+ cli_version: getVersion()
2600
+ };
2601
+ const payload = {
2602
+ command: req.command,
2603
+ requestid: req.requestid,
2604
+ response: JSON.stringify(versionResponse),
2605
+ error: false
2606
+ };
2607
+ ws.send(JSON.stringify(payload));
2608
+ } catch (e) {
2609
+ printer_default.warn(`Error in handleGetVersionReq error: ${String(e)}`);
2610
+ this.sendError(ws, req, String(e));
2611
+ }
2049
2612
  }
2050
2613
  wrapResponse(result, req) {
2051
2614
  if (isErrorResponse(result)) {
@@ -2079,6 +2642,10 @@ var init_websocket_handlers = __esm({
2079
2642
  response = await this.handleStartReq(req, raw);
2080
2643
  break;
2081
2644
  }
2645
+ case "start-v2": {
2646
+ response = await this.handleStartV2Req(req, raw);
2647
+ break;
2648
+ }
2082
2649
  case "stop": {
2083
2650
  response = await this.handleStopReq(req, raw);
2084
2651
  break;
@@ -2095,10 +2662,22 @@ var init_websocket_handlers = __esm({
2095
2662
  response = await this.handleUpdateConfigReq(req, raw);
2096
2663
  break;
2097
2664
  }
2665
+ case "update-config-v2": {
2666
+ response = await this.handleUpdateConfigV2Req(req, raw);
2667
+ break;
2668
+ }
2098
2669
  case "list": {
2099
2670
  response = await this.handleListReq(req);
2100
2671
  break;
2101
2672
  }
2673
+ case "list-v2": {
2674
+ response = await this.handleListV2Req(req);
2675
+ break;
2676
+ }
2677
+ case "get-version": {
2678
+ await this.handleGetVersionReq(ws, req);
2679
+ return;
2680
+ }
2102
2681
  default:
2103
2682
  if (typeof req.command === "string") {
2104
2683
  logger.warn("Unknown command", { command: req.command });
@@ -2145,7 +2724,11 @@ async function parseRemoteManagement(values) {
2145
2724
  if (typeof rmToken === "string" && rmToken.trim().length > 0) {
2146
2725
  const manageHost = values["manage"];
2147
2726
  try {
2148
- await initiateRemoteManagement(rmToken, manageHost);
2727
+ const remoteManagementConfig = {
2728
+ apiKey: rmToken,
2729
+ serverUrl: buildRemoteManagementWsUrl(manageHost)
2730
+ };
2731
+ await initiateRemoteManagement(remoteManagementConfig);
2149
2732
  return { ok: true };
2150
2733
  } catch (e) {
2151
2734
  logger.error("Failed to initiate remote management:", e);
@@ -2153,11 +2736,11 @@ async function parseRemoteManagement(values) {
2153
2736
  }
2154
2737
  }
2155
2738
  }
2156
- async function initiateRemoteManagement(token, manage) {
2157
- if (!token || token.trim().length === 0) {
2739
+ async function initiateRemoteManagement(remoteManagementConfig) {
2740
+ if (!remoteManagementConfig.apiKey || remoteManagementConfig.apiKey.trim().length === 0) {
2158
2741
  throw new Error("Remote management token is required (use --remote-management <TOKEN>)");
2159
2742
  }
2160
- const wsUrl = buildRemoteManagementWsUrl(manage);
2743
+ const wsUrl = remoteManagementConfig.serverUrl;
2161
2744
  const wsHost = extractHostname(wsUrl);
2162
2745
  logger.info("Remote management mode enabled.");
2163
2746
  _stopRequested = false;
@@ -2173,7 +2756,7 @@ async function initiateRemoteManagement(token, manage) {
2173
2756
  logConnecting();
2174
2757
  setRemoteManagementState({ status: RemoteManagementStatus.Connecting, errorMessage: "" });
2175
2758
  try {
2176
- await handleWebSocketConnection(wsUrl, wsHost, token);
2759
+ await handleWebSocketConnection(wsUrl, wsHost, remoteManagementConfig.apiKey);
2177
2760
  } catch (error) {
2178
2761
  logger.warn("Remote management connection error", { error: String(error) });
2179
2762
  }
@@ -2201,6 +2784,7 @@ async function handleWebSocketConnection(wsUrl, wsHost, token) {
2201
2784
  };
2202
2785
  ws.once("open", () => {
2203
2786
  printer_default.success(`Connected to ${wsHost}`);
2787
+ setRemoteManagementState({ status: RemoteManagementStatus.Running, errorMessage: "" });
2204
2788
  heartbeat = setInterval(() => {
2205
2789
  if (ws.readyState === import_ws.default.OPEN) ws.ping();
2206
2790
  }, PING_INTERVAL_MS);
@@ -2211,7 +2795,11 @@ async function handleWebSocketConnection(wsUrl, wsHost, token) {
2211
2795
  if (firstMessage) {
2212
2796
  firstMessage = false;
2213
2797
  const ok = handleConnectionStatusMessage(data);
2214
- if (!ok) ws.close();
2798
+ if (!ok) {
2799
+ ws.close();
2800
+ return;
2801
+ }
2802
+ sendVersionResponse(ws);
2215
2803
  return;
2216
2804
  }
2217
2805
  setRemoteManagementState({ status: RemoteManagementStatus.Running, errorMessage: "" });
@@ -2222,20 +2810,21 @@ async function handleWebSocketConnection(wsUrl, wsHost, token) {
2222
2810
  }
2223
2811
  });
2224
2812
  ws.on("unexpected-response", (_, res) => {
2225
- setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: `HTTP ${res.statusCode}` });
2226
2813
  if (res.statusCode === 401) {
2814
+ setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: `HTTP ${res.statusCode}` });
2227
2815
  printer_default.error("Unauthorized. Please enter a valid token.");
2228
2816
  logger.error("Unauthorized (401) on remote management connect");
2817
+ ws.close();
2229
2818
  } else {
2819
+ logger.warn("Unexpected HTTP response ", { statusCode: res.statusCode });
2230
2820
  printer_default.warn(`Unexpected HTTP ${res.statusCode}. Retrying...`);
2231
- logger.warn("Unexpected HTTP response", { statusCode: res.statusCode });
2821
+ cleanup();
2232
2822
  }
2233
- ws.close();
2234
2823
  });
2235
2824
  ws.on("close", (code, reason) => {
2236
2825
  setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: "" });
2237
2826
  logger.info("WebSocket closed", { code, reason: reason.toString() });
2238
- printer_default.warn(`Disconnected (code: ${code}). Retrying...`);
2827
+ printer_default.warn(`Disconnected (code: ${code}). Retrying in ${RECONNECT_SLEEP_MS / 1e3}s...`);
2239
2828
  cleanup();
2240
2829
  });
2241
2830
  ws.on("error", (err) => {
@@ -2325,6 +2914,7 @@ var init_options = __esm({
2325
2914
  localport: { type: "string", short: "l", description: "Takes input as [protocol:][host:]port. Eg. --localport https://localhost:8000 OR -l 3000" },
2326
2915
  debugger: { type: "string", short: "d", description: "Port for web debugger. Eg. --debugger 4300 OR -d 4300" },
2327
2916
  token: { type: "string", description: "Token for authentication. Eg. --token TOKEN_VALUE" },
2917
+ force: { type: "boolean", short: "f", description: "Forcefully close existing tunnels and establish a new tunnel" },
2328
2918
  // Logging options (CLI overrides env)
2329
2919
  loglevel: { type: "string", description: "Logging level: ERROR, INFO, DEBUG. Overrides PINGGY_LOG_LEVEL environment variable" },
2330
2920
  logfile: { type: "string", description: "Path to log file. Overrides PINGGY_LOG_FILE environment variable" },
@@ -2340,7 +2930,8 @@ var init_options = __esm({
2340
2930
  // Remote Control
2341
2931
  "remote-management": { type: "string", description: "Enable remote management of tunnels with token. Eg. --remote-management API_KEY" },
2342
2932
  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" },
2933
+ noTui: { type: "boolean", description: "Disable TUI in remote management mode" },
2934
+ notui: { type: "boolean", description: "hidden", hidden: true },
2344
2935
  // Misc
2345
2936
  version: { type: "boolean", description: "Print version" },
2346
2937
  // Help
@@ -2421,8 +3012,8 @@ var init_defaults = __esm({
2421
3012
  });
2422
3013
 
2423
3014
  // src/cli/extendedOptions.ts
2424
- function parseExtendedOptions(options, config) {
2425
- if (!options) return;
3015
+ function parseExtendedOptions(options, config, localServerTls) {
3016
+ if (!options) return localServerTls;
2426
3017
  for (const opt of options) {
2427
3018
  const [key, value] = opt.replace(/^"|"$/g, "").split(/:(.+)/).filter(Boolean);
2428
3019
  switch (key) {
@@ -2446,10 +3037,16 @@ function parseExtendedOptions(options, config) {
2446
3037
  case "fullrequesturl":
2447
3038
  config.originalRequestUrl = true;
2448
3039
  break;
2449
- default:
2450
- printer_default.warn(`Unknown extended option "${key}"`);
2451
- logger.warn(`Warning: Unknown extended option "${key}"`);
3040
+ default: {
3041
+ if (value && (value.startsWith("localServerTls") || value.startsWith("localservertls"))) {
3042
+ const parts = value.split(/:(.+)/);
3043
+ localServerTls = parts[1] ? parts[1] : "";
3044
+ } else {
3045
+ printer_default.warn(`Unknown extended option "${value}"`);
3046
+ logger.warn(`Warning: Unknown extended option "${value}"`);
3047
+ }
2452
3048
  break;
3049
+ }
2453
3050
  }
2454
3051
  break;
2455
3052
  case "w":
@@ -2520,6 +3117,7 @@ function parseExtendedOptions(options, config) {
2520
3117
  break;
2521
3118
  }
2522
3119
  }
3120
+ return localServerTls;
2523
3121
  }
2524
3122
  function isValidIpV4Cidr(input) {
2525
3123
  if (input.includes("/")) {
@@ -2570,7 +3168,7 @@ function parseUserAndDomain(str) {
2570
3168
  const [user, domain] = str.split("@", 2);
2571
3169
  if (domainRegex.test(domain)) {
2572
3170
  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)) {
3171
+ if ([import_pinggy4.TunnelType.Http, import_pinggy4.TunnelType.Tcp, import_pinggy4.TunnelType.Tls, import_pinggy4.TunnelType.Udp, import_pinggy4.TunnelType.TlsTcp].includes(keyword)) {
2574
3172
  type = keyword;
2575
3173
  } else if (keyword === "force") {
2576
3174
  forceFlag = true;
@@ -2639,9 +3237,9 @@ function parseUsers(positionalArgs, explicitToken) {
2639
3237
  return { token, server, type, forceFlag, qrCode, remaining };
2640
3238
  }
2641
3239
  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];
3240
+ const t = inferredType || values.type;
3241
+ 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) {
3242
+ return t;
2645
3243
  }
2646
3244
  }
2647
3245
  function parseLocalPort(finalConfig, values) {
@@ -2775,15 +3373,14 @@ function parseAdditionalForwarding(forwarding) {
2775
3373
  return new Error("forwarding address incorrect: invalid local port");
2776
3374
  }
2777
3375
  return {
2778
- protocol,
2779
- remoteDomain: remoteDomainRaw,
2780
- remotePort,
2781
- localDomain,
2782
- localPort
3376
+ type: protocol,
3377
+ listenAddress: `${remoteDomainRaw}:${remotePort}`,
3378
+ address: `${localDomain}:${localPort}`
2783
3379
  };
2784
3380
  }
2785
- function parseReverseTunnelAddr(finalConfig, values) {
3381
+ function parseReverseTunnelAddr(finalConfig, values, primaryType) {
2786
3382
  const reverseTunnel = values.R;
3383
+ let forwardingData = [];
2787
3384
  if ((!Array.isArray(reverseTunnel) || reverseTunnel.length === 0) && !values.localport && !finalConfig.forwarding) {
2788
3385
  return new Error("local port not specified. Please use '-h' option for help.");
2789
3386
  }
@@ -2795,18 +3392,21 @@ function parseReverseTunnelAddr(finalConfig, values) {
2795
3392
  if (slicedForwarding.length === 3) {
2796
3393
  const parsed = parseDefaultForwarding(forwarding);
2797
3394
  if (parsed instanceof Error) return parsed;
2798
- finalConfig.forwarding = `${parsed.localDomain}:${parsed.localPort}`;
3395
+ forwardingData.push({
3396
+ address: `${parsed.localDomain}:${parsed.localPort}`,
3397
+ type: primaryType || import_pinggy4.TunnelType.Http
3398
+ });
2799
3399
  } else if (slicedForwarding.length === 4) {
2800
- finalConfig.additionalForwarding ?? (finalConfig.additionalForwarding = []);
2801
3400
  const parsed = parseAdditionalForwarding(forwarding);
2802
3401
  if (parsed instanceof Error) return parsed;
2803
- finalConfig.additionalForwarding.push(parsed);
3402
+ forwardingData.push(parsed);
2804
3403
  } else {
2805
3404
  return new Error(
2806
3405
  "Incorrect command line arguments: reverse tunnel address incorrect. Please use '-h' option for help."
2807
3406
  );
2808
3407
  }
2809
3408
  }
3409
+ finalConfig.forwarding = forwardingData;
2810
3410
  return null;
2811
3411
  }
2812
3412
  function parseLocalTunnelAddr(finalConfig, values) {
@@ -2842,13 +3442,19 @@ function parseToken(finalConfig, explicitToken) {
2842
3442
  }
2843
3443
  }
2844
3444
  function parseArgs(finalConfig, remainingPositionals) {
2845
- parseExtendedOptions(remainingPositionals, finalConfig);
3445
+ let localserverTls = "";
3446
+ localserverTls = parseExtendedOptions(remainingPositionals, finalConfig, localserverTls);
3447
+ if (localserverTls.length > 0 && finalConfig.forwarding) {
3448
+ if (typeof finalConfig.forwarding[0] === "object" && "address" in finalConfig.forwarding[0]) {
3449
+ finalConfig.forwarding[0].address = `https://${finalConfig.forwarding[0].address}`;
3450
+ }
3451
+ }
2846
3452
  }
2847
3453
  function storeJson(config, saveconf) {
2848
3454
  if (saveconf) {
2849
3455
  const path5 = saveconf;
2850
3456
  try {
2851
- import_fs3.default.writeFileSync(path5, JSON.stringify(config, null, 2), { encoding: "utf-8", flag: "w" });
3457
+ import_fs4.default.writeFileSync(path5, JSON.stringify(config, null, 2), { encoding: "utf-8", flag: "w" });
2852
3458
  logger.info(`Configuration saved to ${path5}`);
2853
3459
  } catch (err) {
2854
3460
  const msg = err instanceof Error ? err.message : String(err);
@@ -2859,9 +3465,9 @@ function storeJson(config, saveconf) {
2859
3465
  function loadJsonConfig(config) {
2860
3466
  const configpath = config["conf"];
2861
3467
  if (typeof configpath === "string" && configpath.trim().length > 0) {
2862
- const filepath = import_path3.default.resolve(configpath);
3468
+ const filepath = import_path4.default.resolve(configpath);
2863
3469
  try {
2864
- const data = import_fs3.default.readFileSync(filepath, { encoding: "utf-8" });
3470
+ const data = import_fs4.default.readFileSync(filepath, { encoding: "utf-8" });
2865
3471
  const json = JSON.parse(data);
2866
3472
  return json;
2867
3473
  } catch (err) {
@@ -2880,7 +3486,7 @@ function isSaveConfOption(values) {
2880
3486
  function parseServe(finalConfig, values) {
2881
3487
  const sv = values.serve;
2882
3488
  if (typeof sv !== "string" || sv.trim().length === 0) return null;
2883
- finalConfig.serve = sv;
3489
+ finalConfig.optional.serve = sv;
2884
3490
  return null;
2885
3491
  }
2886
3492
  function parseAutoReconnect(finalConfig, values) {
@@ -2918,21 +3524,23 @@ async function buildFinalConfig(values, positionals) {
2918
3524
  ...defaultOptions,
2919
3525
  ...configFromFile || {},
2920
3526
  // Apply loaded config on top of defaults
2921
- configid: getRandomId(),
3527
+ configId: getRandomId(),
2922
3528
  token: token || (configFromFile?.token || (typeof values.token === "string" ? values.token : "")),
2923
3529
  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
3530
+ isQRCode: qrCode || (configFromFile?.isQRCode || false),
3531
+ autoReconnect: configFromFile?.autoReconnect ? configFromFile.autoReconnect : defaultOptions.autoReconnect,
3532
+ optional: {
3533
+ serve: configFromFile?.optional?.serve || void 0,
3534
+ noTui: values.noTui || values.notui || (configFromFile?.optional?.noTui || false)
3535
+ }
2928
3536
  };
2929
- parseType(finalConfig, values, type);
3537
+ type = parseType(finalConfig, values, type);
2930
3538
  parseToken(finalConfig, token || values.token);
2931
3539
  const dbgErr = parseDebugger(finalConfig, values);
2932
3540
  if (dbgErr instanceof Error) throw dbgErr;
2933
3541
  const lpErr = parseLocalPort(finalConfig, values);
2934
3542
  if (lpErr instanceof Error) throw lpErr;
2935
- const rErr = parseReverseTunnelAddr(finalConfig, values);
3543
+ const rErr = parseReverseTunnelAddr(finalConfig, values, type);
2936
3544
  if (rErr instanceof Error) throw rErr;
2937
3545
  const lErr = parseLocalTunnelAddr(finalConfig, values);
2938
3546
  if (lErr instanceof Error) throw lErr;
@@ -2940,12 +3548,12 @@ async function buildFinalConfig(values, positionals) {
2940
3548
  if (serveErr instanceof Error) throw serveErr;
2941
3549
  const autoReconnectErr = parseAutoReconnect(finalConfig, values);
2942
3550
  if (autoReconnectErr instanceof Error) throw autoReconnectErr;
2943
- if (forceFlag) finalConfig.force = true;
3551
+ if (forceFlag || values.force) finalConfig.force = true;
2944
3552
  parseArgs(finalConfig, remainingPositionals);
2945
3553
  storeJson(finalConfig, saveconf);
2946
3554
  return finalConfig;
2947
3555
  }
2948
- var import_pinggy5, import_fs3, import_path3, domainRegex, KEYWORDS, VALID_PROTOCOLS;
3556
+ var import_pinggy4, import_fs4, import_path4, domainRegex, KEYWORDS, VALID_PROTOCOLS;
2949
3557
  var init_buildConfig = __esm({
2950
3558
  "src/cli/buildConfig.ts"() {
2951
3559
  "use strict";
@@ -2954,16 +3562,16 @@ var init_buildConfig = __esm({
2954
3562
  init_extendedOptions();
2955
3563
  init_logger();
2956
3564
  init_util();
2957
- import_pinggy5 = require("@pinggy/pinggy");
2958
- import_fs3 = __toESM(require("fs"), 1);
2959
- import_path3 = __toESM(require("path"), 1);
3565
+ import_pinggy4 = require("@pinggy/pinggy");
3566
+ import_fs4 = __toESM(require("fs"), 1);
3567
+ import_path4 = __toESM(require("path"), 1);
2960
3568
  domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
2961
3569
  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,
3570
+ import_pinggy4.TunnelType.Http,
3571
+ import_pinggy4.TunnelType.Tcp,
3572
+ import_pinggy4.TunnelType.Tls,
3573
+ import_pinggy4.TunnelType.Udp,
3574
+ import_pinggy4.TunnelType.TlsTcp,
2967
3575
  "force",
2968
3576
  "qr"
2969
3577
  ]);
@@ -3000,7 +3608,7 @@ function preprocessWindowsArgs(args) {
3000
3608
  function parseCliArgs(options) {
3001
3609
  const rawArgs = process.argv.slice(2);
3002
3610
  const processedArgs = preprocessWindowsArgs(rawArgs);
3003
- const parsed = (0, import_util4.parseArgs)({
3611
+ const parsed = (0, import_util6.parseArgs)({
3004
3612
  args: processedArgs,
3005
3613
  options,
3006
3614
  allowPositionals: true
@@ -3011,12 +3619,12 @@ function parseCliArgs(options) {
3011
3619
  hasAnyArgs
3012
3620
  };
3013
3621
  }
3014
- var import_util4, os2;
3622
+ var import_util6, os2;
3015
3623
  var init_parseArgs = __esm({
3016
3624
  "src/utils/parseArgs.ts"() {
3017
3625
  "use strict";
3018
3626
  init_cjs_shims();
3019
- import_util4 = require("util");
3627
+ import_util6 = require("util");
3020
3628
  os2 = __toESM(require("os"), 1);
3021
3629
  }
3022
3630
  });
@@ -4448,7 +5056,7 @@ async function launchTui(finalConfig, urls, greet, tunnel) {
4448
5056
  }
4449
5057
  }
4450
5058
  async function startCli(finalConfig, manager) {
4451
- if (!finalConfig.NoTUI && finalConfig.webDebugger === "") {
5059
+ if (!finalConfig.optional?.noTui && finalConfig.webDebugger === "") {
4452
5060
  const freePort = await getFreePort(finalConfig.webDebugger || "");
4453
5061
  finalConfig.webDebugger = `localhost:${freePort}`;
4454
5062
  }
@@ -4456,7 +5064,7 @@ async function startCli(finalConfig, manager) {
4456
5064
  const manager2 = TunnelManager.getInstance();
4457
5065
  const tunnel = await manager2.createTunnel(finalConfig);
4458
5066
  printer_default.startSpinner("Connecting to Pinggy...");
4459
- if (!finalConfig.NoTUI) {
5067
+ if (!finalConfig.optional?.noTui) {
4460
5068
  manager2.registerStatsListener(tunnel.tunnelid, (tunnelId, stats) => {
4461
5069
  globalThis.__PINGGY_TUNNEL_STATS__?.(stats);
4462
5070
  });
@@ -4466,26 +5074,26 @@ async function startCli(finalConfig, manager) {
4466
5074
  });
4467
5075
  await manager2.startTunnel(tunnel.tunnelid);
4468
5076
  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"));
5077
+ printer_default.success(import_picocolors4.default.bold("Tunnel established!"));
5078
+ 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
5079
  TunnelData.urls = await manager2.getTunnelUrls(tunnel.tunnelid);
4472
5080
  TunnelData.greet = await manager2.getTunnelGreetMessage(tunnel.tunnelid);
4473
- printer_default.info(import_picocolors3.default.cyanBright("Remote URLs:"));
5081
+ printer_default.info(import_picocolors4.default.cyanBright("Remote URLs:"));
4474
5082
  (TunnelData.urls ?? []).forEach(
4475
- (url) => printer_default.print(" " + import_picocolors3.default.magentaBright(url))
5083
+ (url) => printer_default.print(" " + import_picocolors4.default.magentaBright(url))
4476
5084
  );
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"));
5085
+ 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
5086
  if (TunnelData.greet?.includes("not authenticated")) {
4479
- printer_default.warn(import_picocolors3.default.yellowBright(TunnelData.greet));
5087
+ printer_default.warn(import_picocolors4.default.yellowBright(TunnelData.greet));
4480
5088
  } else if (TunnelData.greet?.includes("authenticated as")) {
4481
5089
  const emailMatch = /authenticated as (.+)/.exec(TunnelData.greet);
4482
5090
  if (emailMatch) {
4483
5091
  const email = emailMatch[1];
4484
- printer_default.info(import_picocolors3.default.cyanBright("Authenticated as: " + email));
5092
+ printer_default.info(import_picocolors4.default.cyanBright("Authenticated as: " + email));
4485
5093
  }
4486
5094
  }
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"));
5095
+ 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"));
5096
+ printer_default.print(import_picocolors4.default.gray("\nPress Ctrl+C to stop the tunnel.\n"));
4489
5097
  manager2.registerWillReconnectListener(tunnel.tunnelid, (tunnelId, error, messages) => {
4490
5098
  if (activeTui) {
4491
5099
  const msg = messages?.join("\n") || error || "Tunnel disconnected, reconnecting...";
@@ -4555,34 +5163,34 @@ async function startCli(finalConfig, manager) {
4555
5163
  printer_default.stopSpinnerSuccess("Reconnected to Pinggy");
4556
5164
  } catch (e) {
4557
5165
  }
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"));
5166
+ printer_default.success(import_picocolors4.default.bold("Tunnel re-established!"));
5167
+ 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
5168
  TunnelData.urls = urls;
4561
5169
  TunnelData.greet = await manager2.getTunnelGreetMessage(tunnel.tunnelid);
4562
- printer_default.info(import_picocolors3.default.cyanBright("Remote URLs:"));
5170
+ printer_default.info(import_picocolors4.default.cyanBright("Remote URLs:"));
4563
5171
  (TunnelData.urls ?? []).forEach(
4564
- (url) => printer_default.print(" " + import_picocolors3.default.magentaBright(url))
5172
+ (url) => printer_default.print(" " + import_picocolors4.default.magentaBright(url))
4565
5173
  );
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"));
5174
+ 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
5175
  if (TunnelData.greet?.includes("not authenticated")) {
4568
- printer_default.warn(import_picocolors3.default.yellowBright(TunnelData.greet));
5176
+ printer_default.warn(import_picocolors4.default.yellowBright(TunnelData.greet));
4569
5177
  } else if (TunnelData.greet?.includes("authenticated as")) {
4570
5178
  const emailMatch = /authenticated as (.+)/.exec(TunnelData.greet);
4571
5179
  if (emailMatch) {
4572
5180
  const email = emailMatch[1];
4573
- printer_default.info(import_picocolors3.default.cyanBright("Authenticated as: " + email));
5181
+ printer_default.info(import_picocolors4.default.cyanBright("Authenticated as: " + email));
4574
5182
  }
4575
5183
  }
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) {
5184
+ 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"));
5185
+ printer_default.print(import_picocolors4.default.gray("\nPress Ctrl+C to stop the tunnel.\n"));
5186
+ if (!finalConfig.optional?.noTui) {
4579
5187
  await launchTui(finalConfig, TunnelData.urls, TunnelData.greet, tunnel);
4580
5188
  }
4581
5189
  });
4582
5190
  } catch (e) {
4583
5191
  logger.debug("Failed to register start listener", e);
4584
5192
  }
4585
- if (!finalConfig.NoTUI) {
5193
+ if (!finalConfig.optional?.noTui) {
4586
5194
  await launchTui(finalConfig, TunnelData.urls, TunnelData.greet, tunnel);
4587
5195
  }
4588
5196
  } catch (err) {
@@ -4591,7 +5199,7 @@ async function startCli(finalConfig, manager) {
4591
5199
  throw err;
4592
5200
  }
4593
5201
  }
4594
- var import_picocolors3, TunnelData, activeTui, disconnectState;
5202
+ var import_picocolors4, TunnelData, activeTui, disconnectState;
4595
5203
  var init_starCli = __esm({
4596
5204
  "src/cli/starCli.ts"() {
4597
5205
  "use strict";
@@ -4600,7 +5208,7 @@ var init_starCli = __esm({
4600
5208
  init_TunnelManager();
4601
5209
  init_getFreePort();
4602
5210
  init_logger();
4603
- import_picocolors3 = __toESM(require("picocolors"), 1);
5211
+ import_picocolors4 = __toESM(require("picocolors"), 1);
4604
5212
  init_blessed();
4605
5213
  TunnelData = {
4606
5214
  urls: null,
@@ -4657,7 +5265,7 @@ async function main() {
4657
5265
  printer_default.error(error);
4658
5266
  }
4659
5267
  }
4660
- var import_url, import_process, import_fs4, currentFile, entryFile;
5268
+ var import_url2, import_process, import_fs5, currentFile, entryFile;
4661
5269
  var init_main = __esm({
4662
5270
  "src/main.ts"() {
4663
5271
  "use strict";
@@ -4673,15 +5281,15 @@ var init_main = __esm({
4673
5281
  init_starCli();
4674
5282
  init_util();
4675
5283
  init_handler();
4676
- import_url = require("url");
5284
+ import_url2 = require("url");
4677
5285
  import_process = require("process");
4678
- import_fs4 = require("fs");
5286
+ import_fs5 = require("fs");
4679
5287
  init_logger();
4680
5288
  init_remoteManagement();
4681
- currentFile = (0, import_url.fileURLToPath)(importMetaUrl);
5289
+ currentFile = (0, import_url2.fileURLToPath)(importMetaUrl);
4682
5290
  entryFile = null;
4683
5291
  try {
4684
- entryFile = import_process.argv[1] ? (0, import_fs4.realpathSync)(import_process.argv[1]) : null;
5292
+ entryFile = import_process.argv[1] ? (0, import_fs5.realpathSync)(import_process.argv[1]) : null;
4685
5293
  } catch (e) {
4686
5294
  entryFile = null;
4687
5295
  }