pinggy 0.4.5 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -116,6 +116,11 @@ var init_printer = __esm({
116
116
  const def = this.errorDefinitions.find((d) => d.match(err));
117
117
  const msg = def.message(err);
118
118
  console.error(import_picocolors2.default.red(import_picocolors2.default.bold("\u2716 Error:")), import_picocolors2.default.red(msg));
119
+ }
120
+ static fatal(err) {
121
+ const def = this.errorDefinitions.find((d) => d.match(err));
122
+ const msg = def.message(err);
123
+ console.error(import_picocolors2.default.red(import_picocolors2.default.bold("\u2716 Fatal Error:")), import_picocolors2.default.red(msg));
119
124
  process.exit(1);
120
125
  }
121
126
  static red(message) {
@@ -254,7 +259,9 @@ function enablePackageLogging(opts) {
254
259
  return applyLoggingConfig(opts ?? {});
255
260
  }
256
261
  function enableLoggingByLogLevelInSdk(loglevel, logFilePath) {
257
- if (!loglevel) return;
262
+ if (!loglevel) {
263
+ return;
264
+ }
258
265
  const l = loglevel.toUpperCase();
259
266
  if (loglevel === "DEBUG") {
260
267
  import_pinggy.pinggy.setDebugLogging(true, import_pinggy.LogLevel.DEBUG, logFilePath);
@@ -430,7 +437,9 @@ var init_TunnelManager = __esm({
430
437
  */
431
438
  async startTunnel(tunnelId) {
432
439
  const managed = this.tunnelsByTunnelId.get(tunnelId);
433
- if (!managed) throw new Error(`Tunnel with id "${tunnelId}" not found`);
440
+ if (!managed) {
441
+ throw new Error(`Tunnel with id "${tunnelId}" not found`);
442
+ }
434
443
  logger.info("Starting tunnel", { tunnelId });
435
444
  let urls;
436
445
  try {
@@ -478,7 +487,9 @@ var init_TunnelManager = __esm({
478
487
  */
479
488
  stopTunnel(tunnelId) {
480
489
  const managed = this.tunnelsByTunnelId.get(tunnelId);
481
- if (!managed) throw new Error(`Tunnel "${tunnelId}" not found`);
490
+ if (!managed) {
491
+ throw new Error(`Tunnel "${tunnelId}" not found`);
492
+ }
482
493
  logger.info("Stopping tunnel", { tunnelId, configId: managed.configId });
483
494
  try {
484
495
  managed.instance.stop();
@@ -659,12 +670,16 @@ var init_TunnelManager = __esm({
659
670
  getTunnelInstance(configId, tunnelId) {
660
671
  if (configId) {
661
672
  const managed = this.tunnelsByConfigId.get(configId);
662
- if (!managed) throw new Error(`Tunnel "${configId}" not found`);
673
+ if (!managed) {
674
+ throw new Error(`Tunnel "${configId}" not found`);
675
+ }
663
676
  return managed.instance;
664
677
  }
665
678
  if (tunnelId) {
666
679
  const managed = this.tunnelsByTunnelId.get(tunnelId);
667
- if (!managed) throw new Error(`Tunnel "${tunnelId}" not found`);
680
+ if (!managed) {
681
+ throw new Error(`Tunnel "${tunnelId}" not found`);
682
+ }
668
683
  return managed.instance;
669
684
  }
670
685
  throw new Error(`Either configId or tunnelId must be provided`);
@@ -843,12 +858,16 @@ var init_TunnelManager = __esm({
843
858
  getManagedTunnel(configId, tunnelId) {
844
859
  if (configId) {
845
860
  const managed = this.tunnelsByConfigId.get(configId);
846
- if (!managed) throw new Error(`Tunnel "${configId}" not found`);
861
+ if (!managed) {
862
+ throw new Error(`Tunnel "${configId}" not found`);
863
+ }
847
864
  return managed;
848
865
  }
849
866
  if (tunnelId) {
850
867
  const managed = this.tunnelsByTunnelId.get(tunnelId);
851
- if (!managed) throw new Error(`Tunnel "${tunnelId}" not found`);
868
+ if (!managed) {
869
+ throw new Error(`Tunnel "${tunnelId}" not found`);
870
+ }
852
871
  return managed;
853
872
  }
854
873
  throw new Error(`Either configId or tunnelId must be provided`);
@@ -1237,7 +1256,9 @@ var init_TunnelManager = __esm({
1237
1256
  notifyPollingErrorListeners(tunnelId, errorMsg) {
1238
1257
  try {
1239
1258
  const listeners = this.tunnelPollingErrorListeners.get(tunnelId);
1240
- if (!listeners) return;
1259
+ if (!listeners) {
1260
+ return;
1261
+ }
1241
1262
  for (const [id, listener] of listeners) {
1242
1263
  try {
1243
1264
  listener(tunnelId, errorMsg);
@@ -1252,7 +1273,9 @@ var init_TunnelManager = __esm({
1252
1273
  notifyErrorListeners(tunnelId, errorMsg, isFatal) {
1253
1274
  try {
1254
1275
  const listeners = this.tunnelErrorListeners.get(tunnelId);
1255
- if (!listeners) return;
1276
+ if (!listeners) {
1277
+ return;
1278
+ }
1256
1279
  for (const [id, listener] of listeners) {
1257
1280
  try {
1258
1281
  listener(tunnelId, errorMsg, isFatal);
@@ -1301,7 +1324,9 @@ var init_TunnelManager = __esm({
1301
1324
  managedTunnel.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
1302
1325
  }
1303
1326
  const listeners = this.tunnelDisconnectListeners.get(tunnelId);
1304
- if (!listeners) return;
1327
+ if (!listeners) {
1328
+ return;
1329
+ }
1305
1330
  for (const [id, listener] of listeners) {
1306
1331
  try {
1307
1332
  listener(tunnelId, error, messages);
@@ -1329,7 +1354,9 @@ var init_TunnelManager = __esm({
1329
1354
  try {
1330
1355
  logger.info("Tunnel will reconnect", { tunnelId, error, messages });
1331
1356
  const listeners = this.tunnelWillReconnectListeners.get(tunnelId);
1332
- if (!listeners) return;
1357
+ if (!listeners) {
1358
+ return;
1359
+ }
1333
1360
  for (const [id, listener] of listeners) {
1334
1361
  try {
1335
1362
  listener(tunnelId, error, messages);
@@ -1357,7 +1384,9 @@ var init_TunnelManager = __esm({
1357
1384
  try {
1358
1385
  logger.info("Tunnel reconnecting", { tunnelId, retryCnt });
1359
1386
  const listeners = this.tunnelReconnectingListeners.get(tunnelId);
1360
- if (!listeners) return;
1387
+ if (!listeners) {
1388
+ return;
1389
+ }
1361
1390
  for (const [id, listener] of listeners) {
1362
1391
  try {
1363
1392
  listener(tunnelId, retryCnt);
@@ -1435,7 +1464,9 @@ var init_TunnelManager = __esm({
1435
1464
  managedTunnel.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
1436
1465
  }
1437
1466
  const listeners = this.tunnelReconnectionFailedListeners.get(tunnelId);
1438
- if (!listeners) return;
1467
+ if (!listeners) {
1468
+ return;
1469
+ }
1439
1470
  for (const [id, listener] of listeners) {
1440
1471
  try {
1441
1472
  listener(tunnelId, retryCnt);
@@ -1459,7 +1490,9 @@ var init_TunnelManager = __esm({
1459
1490
  try {
1460
1491
  logger.debug("Error in Tunnel Worker", { tunnelId, errorMessage: error.message });
1461
1492
  const listeners = this.tunnelWorkerErrorListeners.get(tunnelId);
1462
- if (!listeners) return;
1493
+ if (!listeners) {
1494
+ return;
1495
+ }
1463
1496
  for (const [id, listener] of listeners) {
1464
1497
  try {
1465
1498
  listener(tunnelId, error);
@@ -1945,6 +1978,7 @@ var init_handler = __esm({
1945
1978
  "use strict";
1946
1979
  init_cjs_shims();
1947
1980
  init_types();
1981
+ init_logger();
1948
1982
  init_TunnelManager();
1949
1983
  init_remote_schema();
1950
1984
  TunnelOperations = class {
@@ -1967,6 +2001,26 @@ var init_handler = __esm({
1967
2001
  }
1968
2002
  return status;
1969
2003
  }
2004
+ // --- Placeholder response ---
2005
+ buildPendingTunnelResponse(tunnelid, tunnelConfig, configid, tunnelName, serve) {
2006
+ return {
2007
+ tunnelid,
2008
+ remoteurls: [],
2009
+ tunnelconfig: pinggyOptionsToTunnelConfig(tunnelConfig, configid, tunnelName, false, void 0, serve),
2010
+ status: this.buildStatus(tunnelid, "starting" /* Starting */, "" /* NoError */),
2011
+ stats: newStats()
2012
+ };
2013
+ }
2014
+ buildPendingTunnelResponseV2(tunnelid, tunnelConfig, configFromCli, configid, tunnelName, serve) {
2015
+ return {
2016
+ tunnelid,
2017
+ remoteurls: [],
2018
+ tunnelconfig: pinggyOptionsToTunnelConfigV1(tunnelConfig, configFromCli),
2019
+ status: this.buildStatus(tunnelid, "starting" /* Starting */, "" /* NoError */),
2020
+ stats: newStats(),
2021
+ greetmsg: ""
2022
+ };
2023
+ }
1970
2024
  // --- Helper to construct TunnelResponse ---
1971
2025
  async buildTunnelResponse(tunnelid, tunnelConfig, configid, tunnelName, serve) {
1972
2026
  const [status, stats, tlsInfo, greetMsg, remoteurls] = await Promise.all([
@@ -2007,10 +2061,10 @@ var init_handler = __esm({
2007
2061
  });
2008
2062
  }
2009
2063
  // --- Operations ---
2010
- async handleStart(config) {
2064
+ async handleStart(config, noWait = false) {
2011
2065
  try {
2012
2066
  const opts = tunnelConfigToPinggyOptions(config);
2013
- const { tunnelid, instance, tunnelName, serve, tunnelConfig } = await this.tunnelManager.createTunnel({
2067
+ const managed = await this.tunnelManager.createTunnel({
2014
2068
  ...opts,
2015
2069
  configId: config.configid,
2016
2070
  name: config.configname,
@@ -2018,36 +2072,52 @@ var init_handler = __esm({
2018
2072
  serve: config.serve
2019
2073
  }
2020
2074
  });
2021
- await this.tunnelManager.startTunnel(tunnelid);
2075
+ const { tunnelid, tunnelName, serve, tunnelConfig } = managed;
2076
+ const startPromise = this.tunnelManager.startTunnel(tunnelid);
2077
+ if (noWait) {
2078
+ startPromise.catch((err) => {
2079
+ logger.error("No-wait startTunnel failed", { tunnelid, err: String(err) });
2080
+ });
2081
+ return this.buildPendingTunnelResponse(tunnelid, tunnelConfig, config.configid, tunnelName, serve);
2082
+ }
2083
+ await startPromise;
2022
2084
  const tunnelPconfig = await this.tunnelManager.getTunnelConfig("", tunnelid);
2023
- const resp = this.buildTunnelResponse(tunnelid, tunnelPconfig, config.configid, tunnelName, serve);
2024
- return resp;
2085
+ return this.buildTunnelResponse(tunnelid, tunnelPconfig, config.configid, tunnelName, serve);
2025
2086
  } catch (err) {
2026
2087
  return this.error(ErrorCode.ErrorStartingTunnel, err, "Unknown error occurred while starting tunnel");
2027
2088
  }
2028
2089
  }
2029
- async handleStartV2(config) {
2090
+ async handleStartV2(config, noWait = false) {
2030
2091
  try {
2031
- const { tunnelid, instance, serve } = await this.tunnelManager.createTunnel(config);
2092
+ const managed = await this.tunnelManager.createTunnel(config);
2093
+ const { tunnelid, serve, tunnelConfig } = managed;
2032
2094
  await this.tunnelManager.startTunnel(tunnelid);
2033
2095
  const tunnelPconfig = await this.tunnelManager.getTunnelConfig("", tunnelid);
2034
- const resp = this.buildTunnelResponseV2(tunnelid, tunnelPconfig, config, config.configId, config.name, config.serve);
2035
- return resp;
2096
+ return this.buildTunnelResponseV2(tunnelid, tunnelPconfig, config, config.configId, config.name, config.serve);
2036
2097
  } catch (err) {
2037
2098
  return this.error(ErrorCode.ErrorStartingTunnel, err, "Unknown error occurred while starting tunnel");
2038
2099
  }
2039
2100
  }
2040
- async handleUpdateConfig(config) {
2101
+ async handleUpdateConfig(config, noWait = false) {
2041
2102
  try {
2042
2103
  const opts = tunnelConfigToPinggyOptions(config);
2043
- const tunnel = await this.tunnelManager.updateConfig({
2104
+ const updateOpts = {
2044
2105
  ...opts,
2045
2106
  configId: config.configid,
2046
2107
  name: config.configname,
2047
2108
  optional: {
2048
2109
  serve: config.serve
2049
2110
  }
2050
- });
2111
+ };
2112
+ if (noWait) {
2113
+ const existing = this.tunnelManager.getManagedTunnel(config.configid);
2114
+ if (!existing.tunnelConfig) throw new Error("Invalid tunnel state before configuration update");
2115
+ this.tunnelManager.updateConfig(updateOpts).catch((err) => {
2116
+ logger.error("No-wait updateConfig failed", { configid: config.configid, err: String(err) });
2117
+ });
2118
+ return this.buildPendingTunnelResponse(existing.tunnelid, existing.tunnelConfig, config.configid, existing.tunnelName, existing.serve);
2119
+ }
2120
+ const tunnel = await this.tunnelManager.updateConfig(updateOpts);
2051
2121
  if (!tunnel.instance || !tunnel.tunnelConfig)
2052
2122
  throw new Error("Invalid tunnel state after configuration update");
2053
2123
  return this.buildTunnelResponse(tunnel.tunnelid, tunnel.tunnelConfig, config.configid, tunnel.tunnelName, tunnel.serve);
@@ -2055,8 +2125,17 @@ var init_handler = __esm({
2055
2125
  return this.error(ErrorCode.InternalServerError, err, "Failed to update tunnel configuration");
2056
2126
  }
2057
2127
  }
2058
- async handleUpdateConfigV2(config) {
2128
+ async handleUpdateConfigV2(config, noWait = false) {
2059
2129
  try {
2130
+ if (noWait) {
2131
+ const existing = this.tunnelManager.getManagedTunnel(config.configId);
2132
+ console.log(existing);
2133
+ if (!existing.tunnelConfig) throw new Error("Invalid tunnel state before configuration update");
2134
+ this.tunnelManager.updateConfig(config).catch((err) => {
2135
+ logger.error("No-wait updateConfigV2 failed", { configId: config.configId, err: String(err) });
2136
+ });
2137
+ return this.buildPendingTunnelResponseV2(existing.tunnelid, existing.tunnelConfig, config, config.configId, existing.tunnelName, existing.serve);
2138
+ }
2060
2139
  const tunnel = await this.tunnelManager.updateConfig(config);
2061
2140
  if (!tunnel.instance || !tunnel.tunnelConfig)
2062
2141
  throw new Error("Invalid tunnel state after configuration update");
@@ -2143,8 +2222,16 @@ var init_handler = __esm({
2143
2222
  return this.error(ErrorCode.TunnelNotFound, err, "Failed to get tunnel information");
2144
2223
  }
2145
2224
  }
2146
- async handleRestart(tunnelid) {
2225
+ async handleRestart(tunnelid, noWait = false) {
2147
2226
  try {
2227
+ if (noWait) {
2228
+ const managed2 = this.tunnelManager.getManagedTunnel("", tunnelid);
2229
+ if (!managed2?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
2230
+ this.tunnelManager.restartTunnel(tunnelid).catch((err) => {
2231
+ logger.error("No-wait restartTunnel failed", { tunnelid, err: String(err) });
2232
+ });
2233
+ return this.buildPendingTunnelResponse(tunnelid, managed2.tunnelConfig, managed2.configId, managed2.tunnelName, managed2.serve);
2234
+ }
2148
2235
  await this.tunnelManager.restartTunnel(tunnelid);
2149
2236
  const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
2150
2237
  if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
@@ -2463,7 +2550,7 @@ var init_websocket_handlers = __esm({
2463
2550
  const dc = StartSchema.parse(raw);
2464
2551
  queuedConfig = dc.tunnelConfig;
2465
2552
  remoteManagementWebSocketPrinter.queueStart(dc.tunnelConfig);
2466
- const result = await this.tunnelHandler.handleStart(dc.tunnelConfig);
2553
+ const result = await this.tunnelHandler.handleStart(dc.tunnelConfig, true);
2467
2554
  remoteManagementWebSocketPrinter.handleStartResult(dc.tunnelConfig, result);
2468
2555
  return this.wrapResponse(result, req);
2469
2556
  } catch (e) {
@@ -2484,7 +2571,7 @@ var init_websocket_handlers = __esm({
2484
2571
  const dc = StartV2Schema.parse(raw);
2485
2572
  queuedConfig = dc.tunnelConfig;
2486
2573
  remoteManagementWebSocketPrinter.queueStart(dc.tunnelConfig);
2487
- const result = await this.tunnelHandler.handleStartV2(dc.tunnelConfig);
2574
+ const result = await this.tunnelHandler.handleStartV2(dc.tunnelConfig, true);
2488
2575
  remoteManagementWebSocketPrinter.handleStartResult(dc.tunnelConfig, result);
2489
2576
  return this.wrapResponse(result, req);
2490
2577
  } catch (e) {
@@ -2533,7 +2620,7 @@ var init_websocket_handlers = __esm({
2533
2620
  try {
2534
2621
  const dc = RestartSchema.parse(raw);
2535
2622
  remoteManagementWebSocketPrinter.printRestartRequested(dc.tunnelID);
2536
- const result = await this.tunnelHandler.handleRestart(dc.tunnelID);
2623
+ const result = await this.tunnelHandler.handleRestart(dc.tunnelID, true);
2537
2624
  remoteManagementWebSocketPrinter.handleRestartResult(dc.tunnelID, result);
2538
2625
  return this.wrapResponse(result, req);
2539
2626
  } catch (e) {
@@ -2548,7 +2635,7 @@ var init_websocket_handlers = __esm({
2548
2635
  async handleUpdateConfigReq(req, raw) {
2549
2636
  try {
2550
2637
  const dc = UpdateConfigSchema.parse(raw);
2551
- const result = await this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig);
2638
+ const result = await this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig, true);
2552
2639
  return this.wrapResponse(result, req);
2553
2640
  } catch (e) {
2554
2641
  if (e instanceof import_zod2.default.ZodError) {
@@ -2562,7 +2649,7 @@ var init_websocket_handlers = __esm({
2562
2649
  async handleUpdateConfigV2Req(req, raw) {
2563
2650
  try {
2564
2651
  const dc = UpdateConfigV2Schema.parse(raw);
2565
- const result = await this.tunnelHandler.handleUpdateConfigV2(dc.tunnelConfig);
2652
+ const result = await this.tunnelHandler.handleUpdateConfigV2(dc.tunnelConfig, true);
2566
2653
  return this.wrapResponse(result, req);
2567
2654
  } catch (e) {
2568
2655
  if (e instanceof import_zod2.default.ZodError) {
@@ -2758,9 +2845,14 @@ async function initiateRemoteManagement(remoteManagementConfig) {
2758
2845
  try {
2759
2846
  await handleWebSocketConnection(wsUrl, wsHost, remoteManagementConfig.apiKey);
2760
2847
  } catch (error) {
2848
+ if (error instanceof RemoteManagementUnauthorizedError) {
2849
+ throw error;
2850
+ }
2761
2851
  logger.warn("Remote management connection error", { error: String(error) });
2762
2852
  }
2763
- if (_stopRequested) break;
2853
+ if (_stopRequested) {
2854
+ break;
2855
+ }
2764
2856
  printer_default.warn(`Remote management disconnected. Reconnecting in ${RECONNECT_SLEEP_MS / 1e3} seconds...`);
2765
2857
  logger.info("Reconnecting to remote management after disconnect");
2766
2858
  await sleep(RECONNECT_SLEEP_MS);
@@ -2769,22 +2861,34 @@ async function initiateRemoteManagement(remoteManagementConfig) {
2769
2861
  logger.info("Remote management stopped.");
2770
2862
  return getRemoteManagementState();
2771
2863
  }
2772
- async function handleWebSocketConnection(wsUrl, wsHost, token) {
2773
- return new Promise((resolve) => {
2864
+ async function handleWebSocketConnection(wsUrl, wsHost, token, onOpenCallback) {
2865
+ return new Promise((resolve, reject) => {
2774
2866
  const ws = new import_ws.default(wsUrl, {
2775
2867
  headers: { Authorization: `Bearer ${token}` }
2776
2868
  });
2777
2869
  currentWs = ws;
2778
2870
  let heartbeat = null;
2779
2871
  let firstMessage = true;
2780
- const cleanup = () => {
2781
- if (heartbeat) clearInterval(heartbeat);
2872
+ let settled = false;
2873
+ const cleanup = (err) => {
2874
+ if (settled) {
2875
+ return;
2876
+ }
2877
+ settled = true;
2878
+ if (heartbeat) {
2879
+ clearInterval(heartbeat);
2880
+ }
2782
2881
  currentWs = null;
2783
- resolve();
2882
+ if (err) {
2883
+ reject(err);
2884
+ } else {
2885
+ resolve();
2886
+ }
2784
2887
  };
2785
2888
  ws.once("open", () => {
2786
2889
  printer_default.success(`Connected to ${wsHost}`);
2787
2890
  setRemoteManagementState({ status: RemoteManagementStatus.Running, errorMessage: "" });
2891
+ onOpenCallback?.();
2788
2892
  heartbeat = setInterval(() => {
2789
2893
  if (ws.readyState === import_ws.default.OPEN) ws.ping();
2790
2894
  }, PING_INTERVAL_MS);
@@ -2812,13 +2916,14 @@ async function handleWebSocketConnection(wsUrl, wsHost, token) {
2812
2916
  ws.on("unexpected-response", (_, res) => {
2813
2917
  if (res.statusCode === 401) {
2814
2918
  setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: `HTTP ${res.statusCode}` });
2815
- printer_default.error("Unauthorized. Please enter a valid token.");
2816
2919
  logger.error("Unauthorized (401) on remote management connect");
2920
+ cleanup(new RemoteManagementUnauthorizedError());
2817
2921
  ws.close();
2818
2922
  } else {
2819
2923
  logger.warn("Unexpected HTTP response ", { statusCode: res.statusCode });
2820
2924
  printer_default.warn(`Unexpected HTTP ${res.statusCode}. Retrying...`);
2821
2925
  cleanup();
2926
+ ws.close();
2822
2927
  }
2823
2928
  });
2824
2929
  ws.on("close", (code, reason) => {
@@ -2830,7 +2935,7 @@ async function handleWebSocketConnection(wsUrl, wsHost, token) {
2830
2935
  ws.on("error", (err) => {
2831
2936
  setRemoteManagementState({ status: RemoteManagementStatus.Error, errorMessage: err.message });
2832
2937
  logger.warn("WebSocket error", { error: err.message });
2833
- printer_default.error(err);
2938
+ printer_default.warn(err.message);
2834
2939
  cleanup();
2835
2940
  });
2836
2941
  });
@@ -2860,6 +2965,58 @@ async function closeRemoteManagement(timeoutMs = 1e4) {
2860
2965
  return getRemoteManagementState();
2861
2966
  }
2862
2967
  }
2968
+ function startRemoteManagement(remoteManagementConfig) {
2969
+ if (!remoteManagementConfig.apiKey || remoteManagementConfig.apiKey.trim().length === 0) {
2970
+ return Promise.reject(new Error("Remote management token is required"));
2971
+ }
2972
+ const wsUrl = remoteManagementConfig.serverUrl;
2973
+ const wsHost = extractHostname(wsUrl);
2974
+ logger.info("Remote management mode enabled.");
2975
+ _stopRequested = false;
2976
+ return new Promise((resolve, reject) => {
2977
+ let firstSettled = false;
2978
+ const settleOnce = (err) => {
2979
+ if (firstSettled) {
2980
+ return;
2981
+ }
2982
+ firstSettled = true;
2983
+ if (err) {
2984
+ reject(err);
2985
+ } else {
2986
+ resolve(getRemoteManagementState());
2987
+ }
2988
+ };
2989
+ const runLoop = async () => {
2990
+ const sigintHandler = () => {
2991
+ _stopRequested = true;
2992
+ };
2993
+ process.once("SIGINT", sigintHandler);
2994
+ while (!_stopRequested) {
2995
+ logger.info("Connecting to remote management", { wsUrl });
2996
+ setRemoteManagementState({ status: RemoteManagementStatus.Connecting, errorMessage: "" });
2997
+ try {
2998
+ await handleWebSocketConnection(wsUrl, wsHost, remoteManagementConfig.apiKey, () => settleOnce());
2999
+ } catch (error) {
3000
+ if (error instanceof RemoteManagementUnauthorizedError) {
3001
+ settleOnce(error);
3002
+ process.removeListener("SIGINT", sigintHandler);
3003
+ return;
3004
+ }
3005
+ settleOnce();
3006
+ logger.warn("Remote management connection error", { error: String(error) });
3007
+ }
3008
+ if (_stopRequested) {
3009
+ break;
3010
+ }
3011
+ logger.info("Reconnecting to remote management after disconnect");
3012
+ await sleep(RECONNECT_SLEEP_MS);
3013
+ }
3014
+ process.removeListener("SIGINT", sigintHandler);
3015
+ logger.info("Remote management stopped.");
3016
+ };
3017
+ runLoop().catch((err) => settleOnce(err instanceof Error ? err : new Error(String(err))));
3018
+ });
3019
+ }
2863
3020
  function getRemoteManagementState() {
2864
3021
  return _remoteManagementState;
2865
3022
  }
@@ -2869,7 +3026,7 @@ function setRemoteManagementState(state, errorMessage) {
2869
3026
  errorMessage: errorMessage || ""
2870
3027
  };
2871
3028
  }
2872
- var import_ws, RECONNECT_SLEEP_MS, PING_INTERVAL_MS, _remoteManagementState, _stopRequested, currentWs;
3029
+ var import_ws, RECONNECT_SLEEP_MS, PING_INTERVAL_MS, RemoteManagementUnauthorizedError, _remoteManagementState, _stopRequested, currentWs;
2873
3030
  var init_remoteManagement = __esm({
2874
3031
  "src/remote_management/remoteManagement.ts"() {
2875
3032
  "use strict";
@@ -2881,6 +3038,12 @@ var init_remoteManagement = __esm({
2881
3038
  init_types();
2882
3039
  RECONNECT_SLEEP_MS = 5e3;
2883
3040
  PING_INTERVAL_MS = 3e4;
3041
+ RemoteManagementUnauthorizedError = class extends Error {
3042
+ constructor() {
3043
+ super("Unauthorized. Please enter a valid token.");
3044
+ this.name = "RemoteManagementUnauthorizedError";
3045
+ }
3046
+ };
2884
3047
  _remoteManagementState = {
2885
3048
  status: "NOT_RUNNING",
2886
3049
  errorMessage: ""
@@ -3162,7 +3325,9 @@ function removeIPv6Brackets(ip) {
3162
3325
  }
3163
3326
  function isValidServerAddress(host) {
3164
3327
  const normalized = removeIPv6Brackets(host.trim());
3165
- if (!normalized) return false;
3328
+ if (!normalized) {
3329
+ return false;
3330
+ }
3166
3331
  return domainRegex.test(normalized) || (0, import_net2.isIP)(normalized) !== 0;
3167
3332
  }
3168
3333
  function isKeyword(str) {
@@ -3174,7 +3339,9 @@ function parseUserAndDomain(str) {
3174
3339
  let server;
3175
3340
  let qrCode;
3176
3341
  let forceFlag;
3177
- if (!str) return { token, type, server, qrCode, forceFlag };
3342
+ if (!str) {
3343
+ return { token, type, server, qrCode, forceFlag };
3344
+ }
3178
3345
  if (str.includes("@")) {
3179
3346
  const [user, domain] = str.split("@", 2);
3180
3347
  if (isValidServerAddress(domain)) {
@@ -3227,21 +3394,39 @@ function parseUsers(positionalArgs, explicitToken) {
3227
3394
  let remaining = [...positionalArgs];
3228
3395
  if (typeof explicitToken === "string") {
3229
3396
  const parsed = parseUserAndDomain(explicitToken);
3230
- if (parsed.server) server = parsed.server;
3231
- if (parsed.type) type = parsed.type;
3232
- if (parsed.token) token = parsed.token;
3233
- if (parsed.forceFlag) forceFlag = true;
3234
- if (parsed.qrCode) qrCode = true;
3397
+ if (parsed.server) {
3398
+ server = parsed.server;
3399
+ }
3400
+ if (parsed.type) {
3401
+ type = parsed.type;
3402
+ }
3403
+ if (parsed.token) {
3404
+ token = parsed.token;
3405
+ }
3406
+ if (parsed.forceFlag) {
3407
+ forceFlag = true;
3408
+ }
3409
+ if (parsed.qrCode) {
3410
+ qrCode = true;
3411
+ }
3235
3412
  }
3236
3413
  if (remaining.length > 0) {
3237
3414
  const first = remaining[0];
3238
3415
  const parsed = parseUserAndDomain(first);
3239
3416
  if (parsed.server) {
3240
3417
  server = parsed.server;
3241
- if (parsed.type) type = parsed.type;
3242
- if (parsed.token) token = parsed.token;
3243
- if (parsed.forceFlag) forceFlag = true;
3244
- if (parsed.qrCode) qrCode = true;
3418
+ if (parsed.type) {
3419
+ type = parsed.type;
3420
+ }
3421
+ if (parsed.token) {
3422
+ token = parsed.token;
3423
+ }
3424
+ if (parsed.forceFlag) {
3425
+ forceFlag = true;
3426
+ }
3427
+ if (parsed.qrCode) {
3428
+ qrCode = true;
3429
+ }
3245
3430
  remaining = remaining.slice(1);
3246
3431
  }
3247
3432
  }
@@ -3254,7 +3439,9 @@ function parseType(finalConfig, values, inferredType) {
3254
3439
  }
3255
3440
  }
3256
3441
  function parseLocalPort(finalConfig, values) {
3257
- if (typeof values.localport !== "string") return null;
3442
+ if (typeof values.localport !== "string") {
3443
+ return null;
3444
+ }
3258
3445
  let lp = values.localport.trim();
3259
3446
  let isHttps = false;
3260
3447
  if (lp.startsWith("https://")) {
@@ -3286,7 +3473,9 @@ function parseLocalPort(finalConfig, values) {
3286
3473
  }
3287
3474
  function isValidHostAddress(host) {
3288
3475
  const normalized = removeIPv6Brackets(host.trim());
3289
- if (normalized.length === 0) return false;
3476
+ if (normalized.length === 0) {
3477
+ return false;
3478
+ }
3290
3479
  return normalized === "localhost" || (0, import_net2.isIP)(normalized) !== 0;
3291
3480
  }
3292
3481
  function ipv6SafeSplitColon(s) {
@@ -3329,7 +3518,9 @@ function parseDefaultForwarding(forwarding) {
3329
3518
  }
3330
3519
  function parseAdditionalForwarding(forwarding) {
3331
3520
  const toPort = (v) => {
3332
- if (!v) return null;
3521
+ if (!v) {
3522
+ return null;
3523
+ }
3333
3524
  const n = parseInt(v, 10);
3334
3525
  return Number.isNaN(n) ? null : n;
3335
3526
  };
@@ -3408,7 +3599,9 @@ function parseReverseTunnelAddr(finalConfig, values, primaryType) {
3408
3599
  });
3409
3600
  } else if (slicedForwarding.length === 4) {
3410
3601
  const parsed = parseAdditionalForwarding(forwarding);
3411
- if (parsed instanceof Error) return parsed;
3602
+ if (parsed instanceof Error) {
3603
+ return parsed;
3604
+ }
3412
3605
  forwardingData.push(parsed);
3413
3606
  } else {
3414
3607
  return new Error(
@@ -3420,7 +3613,9 @@ function parseReverseTunnelAddr(finalConfig, values, primaryType) {
3420
3613
  return null;
3421
3614
  }
3422
3615
  function parseLocalTunnelAddr(finalConfig, values) {
3423
- if (!Array.isArray(values.L) || values.L.length === 0) return null;
3616
+ if (!Array.isArray(values.L) || values.L.length === 0) {
3617
+ return null;
3618
+ }
3424
3619
  const firstL = values.L[0];
3425
3620
  const parts = ipv6SafeSplitColon(firstL);
3426
3621
  let debuggerHost = "localhost";
@@ -3444,7 +3639,9 @@ function parseLocalTunnelAddr(finalConfig, values) {
3444
3639
  }
3445
3640
  function parseDebugger(finalConfig, values) {
3446
3641
  let dbg = values.debugger;
3447
- if (typeof dbg !== "string") return;
3642
+ if (typeof dbg !== "string") {
3643
+ return;
3644
+ }
3448
3645
  dbg = dbg.startsWith(":") ? dbg.slice(1) : dbg;
3449
3646
  const d = parseInt(dbg, 10);
3450
3647
  if (!Number.isNaN(d) && isValidPort(d)) {
@@ -3503,7 +3700,9 @@ function isSaveConfOption(values) {
3503
3700
  }
3504
3701
  function parseServe(finalConfig, values) {
3505
3702
  const sv = values.serve;
3506
- if (typeof sv !== "string" || sv.trim().length === 0) return null;
3703
+ if (typeof sv !== "string" || sv.trim().length === 0) {
3704
+ return null;
3705
+ }
3507
3706
  finalConfig.optional.serve = sv;
3508
3707
  return null;
3509
3708
  }
@@ -3555,18 +3754,32 @@ async function buildFinalConfig(values, positionals) {
3555
3754
  type = parseType(finalConfig, values, type);
3556
3755
  parseToken(finalConfig, token || values.token);
3557
3756
  const dbgErr = parseDebugger(finalConfig, values);
3558
- if (dbgErr instanceof Error) throw dbgErr;
3757
+ if (dbgErr instanceof Error) {
3758
+ throw dbgErr;
3759
+ }
3559
3760
  const lpErr = parseLocalPort(finalConfig, values);
3560
- if (lpErr instanceof Error) throw lpErr;
3761
+ if (lpErr instanceof Error) {
3762
+ throw lpErr;
3763
+ }
3561
3764
  const rErr = parseReverseTunnelAddr(finalConfig, values, type);
3562
- if (rErr instanceof Error) throw rErr;
3765
+ if (rErr instanceof Error) {
3766
+ throw rErr;
3767
+ }
3563
3768
  const lErr = parseLocalTunnelAddr(finalConfig, values);
3564
- if (lErr instanceof Error) throw lErr;
3769
+ if (lErr instanceof Error) {
3770
+ throw lErr;
3771
+ }
3565
3772
  const serveErr = parseServe(finalConfig, values);
3566
- if (serveErr instanceof Error) throw serveErr;
3773
+ if (serveErr instanceof Error) {
3774
+ throw serveErr;
3775
+ }
3567
3776
  const autoReconnectErr = parseAutoReconnect(finalConfig, values);
3568
- if (autoReconnectErr instanceof Error) throw autoReconnectErr;
3569
- if (forceFlag || values.force) finalConfig.force = true;
3777
+ if (autoReconnectErr instanceof Error) {
3778
+ throw autoReconnectErr;
3779
+ }
3780
+ if (forceFlag || values.force) {
3781
+ finalConfig.force = true;
3782
+ }
3570
3783
  parseArgs(finalConfig, remainingPositionals);
3571
3784
  storeJson(finalConfig, saveconf);
3572
3785
  return finalConfig;
@@ -3603,15 +3816,26 @@ function isAttachedReverseOrLocalFlag(arg) {
3603
3816
  return /^-[RL].+/.test(arg);
3604
3817
  }
3605
3818
  function shouldMergeReverseOrLocalFragment(current, next) {
3606
- if (next.startsWith("-")) return false;
3607
- if (next.startsWith(".")) return true;
3819
+ if (next.startsWith("-")) {
3820
+ return false;
3821
+ }
3822
+ if (next.startsWith(".")) {
3823
+ return true;
3824
+ }
3608
3825
  const body = current.slice(2);
3609
- if (body.endsWith(":")) return true;
3610
- if (body.includes("//") && !body.includes(":")) return true;
3826
+ if (body.endsWith(":")) {
3827
+ return true;
3828
+ }
3829
+ if (body.includes("//") && !body.includes(":")) {
3830
+ return true;
3831
+ }
3611
3832
  return false;
3612
3833
  }
3613
3834
  function preprocessWindowsArgs(args) {
3614
- if (os2.platform() !== "win32") return args;
3835
+ if (os2.platform() !== "win32") {
3836
+ return args;
3837
+ }
3838
+ ;
3615
3839
  const out = [];
3616
3840
  let i = 0;
3617
3841
  while (i < args.length) {
@@ -3698,13 +3922,12 @@ var init_getFreePort = __esm({
3698
3922
  async function createQrCodes(urls) {
3699
3923
  const codes = [];
3700
3924
  for (const url of urls) {
3701
- const qr = await import_qrcode.default.toString(url, {
3702
- type: "terminal",
3703
- small: true,
3704
- margin: 0,
3925
+ const raw = await import_qrcode.default.toString(url, {
3926
+ type: "utf8",
3927
+ margin: 2,
3705
3928
  errorCorrectionLevel: "L"
3706
3929
  });
3707
- codes.push(qr);
3930
+ codes.push(raw);
3708
3931
  }
3709
3932
  return codes;
3710
3933
  }
@@ -3722,6 +3945,7 @@ function getTuiConfig() {
3722
3945
  return {
3723
3946
  maxRequestPairs: defaultTuiConfig.maxRequestPairs,
3724
3947
  visibleRequestCount: defaultTuiConfig.visibleRequestCount,
3948
+ visibleUrlCount: defaultTuiConfig.visibleUrlCount,
3725
3949
  viewportScrollMargin: defaultTuiConfig.viewportScrollMargin,
3726
3950
  inactivityHttpSelectorTimeoutMs: defaultTuiConfig.inactivityHttpSelectorTimeoutMs
3727
3951
  };
@@ -3734,6 +3958,7 @@ var init_config = __esm({
3734
3958
  defaultTuiConfig = {
3735
3959
  maxRequestPairs: 100,
3736
3960
  visibleRequestCount: 10,
3961
+ visibleUrlCount: 7,
3737
3962
  viewportScrollMargin: 2,
3738
3963
  inactivityHttpSelectorTimeoutMs: 1e4
3739
3964
  };
@@ -3974,7 +4199,7 @@ function createFullUI(screen, urls, greet, tunnelConfig) {
3974
4199
  width: "100%-2",
3975
4200
  height: `100%-${lowerSectionTop + 6}`
3976
4201
  });
3977
- const isQrCodeRequested = tunnelConfig?.qrCode || false;
4202
+ const isQrCodeRequested = tunnelConfig?.isQRCode || false;
3978
4203
  const requestsBox = import_blessed.default.box({
3979
4204
  parent: lowerSection,
3980
4205
  top: 0,
@@ -4134,8 +4359,24 @@ var init_utils = __esm({
4134
4359
  // src/tui/blessed/components/DisplayUpdaters.ts
4135
4360
  function updateUrlsDisplay(urlsBox, screen, urls, currentQrIndex) {
4136
4361
  if (!urlsBox) return;
4137
- let content = "{green-fg}{bold}Public URLs{/bold}{/green-fg}\n";
4138
- urls.forEach((url, index) => {
4362
+ const config = getTuiConfig();
4363
+ const { visibleUrlCount } = config;
4364
+ let viewportStart = 0;
4365
+ if (urls.length > visibleUrlCount) {
4366
+ viewportStart = Math.max(0, Math.min(
4367
+ currentQrIndex - Math.floor(visibleUrlCount / 2),
4368
+ urls.length - visibleUrlCount
4369
+ ));
4370
+ }
4371
+ const viewportEnd = Math.min(viewportStart + visibleUrlCount, urls.length);
4372
+ const visibleUrls = urls.slice(viewportStart, viewportEnd);
4373
+ let content = "{green-fg}{bold}Public URLs{/bold}{/green-fg}";
4374
+ if (viewportStart > 0) {
4375
+ content += ` {gray-fg}\u2191 ${viewportStart} more{/gray-fg}`;
4376
+ }
4377
+ content += "\n";
4378
+ visibleUrls.forEach((url, i) => {
4379
+ const index = viewportStart + i;
4139
4380
  const isSelected = index === currentQrIndex;
4140
4381
  const prefix = isSelected ? "\u2192 " : "\u2022 ";
4141
4382
  const color = isSelected ? "yellow" : "magenta";
@@ -4147,6 +4388,11 @@ function updateUrlsDisplay(urlsBox, screen, urls, currentQrIndex) {
4147
4388
  `;
4148
4389
  }
4149
4390
  });
4391
+ const itemsBelow = urls.length - viewportEnd;
4392
+ if (itemsBelow > 0) {
4393
+ content += `{gray-fg}\u2193 ${itemsBelow} more{/gray-fg}
4394
+ `;
4395
+ }
4150
4396
  urlsBox.setContent(content);
4151
4397
  screen.render();
4152
4398
  }
@@ -4859,7 +5105,7 @@ var init_TunnelTui = __esm({
4859
5105
  }
4860
5106
  }
4861
5107
  async generateQrCodes() {
4862
- if (this.tunnelConfig?.qrCode && this.urls.length > 0) {
5108
+ if (this.tunnelConfig?.isQRCode && this.urls.length > 0) {
4863
5109
  this.qrCodes = await createQrCodes(this.urls);
4864
5110
  this.updateQrCodeDisplay();
4865
5111
  }
@@ -5096,7 +5342,7 @@ async function startCli(finalConfig, manager) {
5096
5342
  });
5097
5343
  }
5098
5344
  manager2.registerWorkerErrorListner(tunnel.tunnelid, (_tunnelid, error) => {
5099
- printer_default.error(`${error.message}`);
5345
+ printer_default.fatal(`${error.message}`);
5100
5346
  });
5101
5347
  await manager2.startTunnel(tunnel.tunnelid);
5102
5348
  printer_default.stopSpinnerSuccess(" Connected to Pinggy");
@@ -5221,7 +5467,7 @@ async function startCli(finalConfig, manager) {
5221
5467
  }
5222
5468
  } catch (err) {
5223
5469
  printer_default.stopSpinnerFail("Failed to connect");
5224
- printer_default.error(err.message || "Unknown error");
5470
+ printer_default.fatal(err.message || "Unknown error");
5225
5471
  throw err;
5226
5472
  }
5227
5473
  }
@@ -5249,6 +5495,7 @@ var init_starCli = __esm({
5249
5495
  // src/main.ts
5250
5496
  var main_exports = {};
5251
5497
  __export(main_exports, {
5498
+ RemoteManagementUnauthorizedError: () => RemoteManagementUnauthorizedError,
5252
5499
  TunnelManager: () => TunnelManager,
5253
5500
  TunnelOperations: () => TunnelOperations,
5254
5501
  closeRemoteManagement: () => closeRemoteManagement,
@@ -5278,9 +5525,8 @@ async function main() {
5278
5525
  }
5279
5526
  const parseResult = await parseRemoteManagement(values);
5280
5527
  if (parseResult?.ok === false) {
5281
- printer_default.error(parseResult.error);
5282
5528
  logger.error("Failed to initiate remote management:", parseResult.error);
5283
- process.exit(1);
5529
+ printer_default.fatal(parseResult.error);
5284
5530
  }
5285
5531
  logger.debug("Building final config from CLI values and positionals", { values, positionals });
5286
5532
  const finalConfig = await buildFinalConfig(values, positionals);
@@ -5288,7 +5534,7 @@ async function main() {
5288
5534
  await startCli(finalConfig, manager);
5289
5535
  } catch (error) {
5290
5536
  logger.error("Unhandled error in CLI:", error);
5291
- printer_default.error(error);
5537
+ printer_default.fatal(error);
5292
5538
  }
5293
5539
  }
5294
5540
  var import_url2, import_process, import_fs5, currentFile, entryFile;
@@ -5328,6 +5574,7 @@ var init_main = __esm({
5328
5574
  // src/index.ts
5329
5575
  var index_exports = {};
5330
5576
  __export(index_exports, {
5577
+ RemoteManagementUnauthorizedError: () => RemoteManagementUnauthorizedError,
5331
5578
  TunnelErrorCodeType: () => TunnelErrorCodeType,
5332
5579
  TunnelManager: () => TunnelManager,
5333
5580
  TunnelOperations: () => TunnelOperations,
@@ -5336,7 +5583,8 @@ __export(index_exports, {
5336
5583
  closeRemoteManagement: () => closeRemoteManagement,
5337
5584
  enablePackageLogging: () => enablePackageLogging,
5338
5585
  getRemoteManagementState: () => getRemoteManagementState,
5339
- initiateRemoteManagement: () => initiateRemoteManagement
5586
+ initiateRemoteManagement: () => initiateRemoteManagement,
5587
+ startRemoteManagement: () => startRemoteManagement
5340
5588
  });
5341
5589
  module.exports = __toCommonJS(index_exports);
5342
5590
  init_cjs_shims();
@@ -5442,11 +5690,11 @@ async function verifyAndLoad() {
5442
5690
  await Promise.resolve().then(() => (init_main(), main_exports));
5443
5691
  }
5444
5692
  verifyAndLoad().catch((err) => {
5445
- printer_default.error(`Failed to start CLI:, ${err}`);
5446
- process.exit(1);
5693
+ printer_default.fatal(`Failed to start CLI:, ${err}`);
5447
5694
  });
5448
5695
  // Annotate the CommonJS export names for ESM import in node:
5449
5696
  0 && (module.exports = {
5697
+ RemoteManagementUnauthorizedError,
5450
5698
  TunnelErrorCodeType,
5451
5699
  TunnelManager,
5452
5700
  TunnelOperations,
@@ -5455,5 +5703,6 @@ verifyAndLoad().catch((err) => {
5455
5703
  closeRemoteManagement,
5456
5704
  enablePackageLogging,
5457
5705
  getRemoteManagementState,
5458
- initiateRemoteManagement
5706
+ initiateRemoteManagement,
5707
+ startRemoteManagement
5459
5708
  });