pinggy 0.6.0 → 0.6.3

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.
@@ -4,10 +4,10 @@ import {
4
4
  import {
5
5
  getLogLevel,
6
6
  logger
7
- } from "./chunk-76SOSXXR.js";
7
+ } from "./chunk-MV56U3E6.js";
8
8
  import {
9
9
  getTunnelLogPath
10
- } from "./chunk-GBYF2H4H.js";
10
+ } from "./chunk-AFMTIELL.js";
11
11
 
12
12
  // src/tunnel_manager/TunnelManager.ts
13
13
  import { TunnelInstance, LogLevel as SdkLogLevel } from "@pinggy/pinggy";
@@ -94,6 +94,114 @@ function maybeRotate(file, maxBytes = DEFAULT_MAX_BYTES, maxArchives = DEFAULT_M
94
94
  }
95
95
  }
96
96
 
97
+ // src/types.ts
98
+ var TunnelStateType = /* @__PURE__ */ ((TunnelStateType2) => {
99
+ TunnelStateType2["New"] = "idle";
100
+ TunnelStateType2["Starting"] = "starting";
101
+ TunnelStateType2["Running"] = "running";
102
+ TunnelStateType2["Live"] = "live";
103
+ TunnelStateType2["Closed"] = "closed";
104
+ TunnelStateType2["Exited"] = "exited";
105
+ return TunnelStateType2;
106
+ })(TunnelStateType || {});
107
+ var TunnelErrorCodeType = /* @__PURE__ */ ((TunnelErrorCodeType2) => {
108
+ TunnelErrorCodeType2["NonResponsive"] = "non_responsive";
109
+ TunnelErrorCodeType2["FailedToConnect"] = "failed_to_connect";
110
+ TunnelErrorCodeType2["ErrorInAdditionalForwarding"] = "additional_forwarding_error";
111
+ TunnelErrorCodeType2["WebdebuggerError"] = "webdebugger_error";
112
+ TunnelErrorCodeType2["NoError"] = "";
113
+ return TunnelErrorCodeType2;
114
+ })(TunnelErrorCodeType || {});
115
+ var TunnelWarningCode = /* @__PURE__ */ ((TunnelWarningCode3) => {
116
+ TunnelWarningCode3["InvalidTunnelServePath"] = "INVALID_TUNNEL_SERVE_PATH";
117
+ TunnelWarningCode3["UnknownWarning"] = "UNKNOWN_WARNING";
118
+ return TunnelWarningCode3;
119
+ })(TunnelWarningCode || {});
120
+ var ErrorCode = {
121
+ InvalidRequestMethodError: "INVALID_REQUEST_METHOD",
122
+ InvalidRequestBodyError: "COULD_NOT_READ_BODY",
123
+ InternalServerError: "INTERNAL_SERVER_ERROR",
124
+ InvalidBodyFormatError: "INVALID_DATA_FORMAT",
125
+ ErrorStartingTunnel: "ERROR_STARTING_TUNNEL",
126
+ TunnelNotFound: "TUNNEL_WITH_ID_OR_CONFIG_ID_NOT_FOUND",
127
+ TunnelAlreadyRunningError: "TUNNEL_WITH_ID_OR_CONFIG_ID_ALREADY_RUNNING",
128
+ WebsocketUpgradeFailError: "WEBSOCKET_UPGRADE_FAILED",
129
+ RemoteManagementAlreadyRunning: "REMOTE_MANAGEMENT_ALREADY_RUNNING",
130
+ RemoteManagementNotRunning: "REMOTE_MANAGEMENT_NOT_RUNNING",
131
+ RemoteManagementDeserializationFailed: "REMOTE_MANAGEMENT_DESERIALIZATION_FAILED",
132
+ DeviceNotEnrolled: "DEVICE_NOT_ENROLLED"
133
+ };
134
+ function isErrorResponse(obj) {
135
+ return typeof obj === "object" && obj !== null && "code" in obj && "message" in obj && typeof obj.message === "string" && Object.values(ErrorCode).includes(obj.code);
136
+ }
137
+ function newErrorResponse(codeOrError, message) {
138
+ if (typeof codeOrError === "object") {
139
+ return codeOrError;
140
+ }
141
+ return {
142
+ code: codeOrError,
143
+ message
144
+ };
145
+ }
146
+ function NewResponseObject(data) {
147
+ const encoder = new TextEncoder();
148
+ const bytes = encoder.encode(JSON.stringify(data));
149
+ return {
150
+ response: bytes,
151
+ requestid: "",
152
+ command: "",
153
+ error: false,
154
+ errorresponse: {}
155
+ };
156
+ }
157
+ function NewErrorResponseObject(errorResponse) {
158
+ return {
159
+ response: new Uint8Array(),
160
+ requestid: "",
161
+ command: "",
162
+ error: true,
163
+ errorresponse: errorResponse
164
+ };
165
+ }
166
+ function newStatus(tunnelState, errorCode, errorMsg) {
167
+ let assignedState = tunnelState;
168
+ if (tunnelState === "live" /* Live */) {
169
+ assignedState = "running" /* Running */;
170
+ } else if (tunnelState === "idle" /* New */) {
171
+ assignedState = "idle" /* New */;
172
+ } else if (tunnelState === "closed" /* Closed */) {
173
+ assignedState = "exited" /* Exited */;
174
+ }
175
+ const now = (/* @__PURE__ */ new Date()).toISOString();
176
+ return {
177
+ state: assignedState,
178
+ errorcode: errorCode,
179
+ errormsg: errorMsg,
180
+ createdtimestamp: now,
181
+ starttimestamp: now,
182
+ endtimestamp: now,
183
+ warnings: []
184
+ };
185
+ }
186
+ function newStats() {
187
+ return {
188
+ numLiveConnections: 0,
189
+ numTotalConnections: 0,
190
+ numTotalReqBytes: 0,
191
+ numTotalResBytes: 0,
192
+ numTotalTxBytes: 0,
193
+ elapsedTime: 0
194
+ };
195
+ }
196
+ var RemoteManagementStatus = {
197
+ Connecting: "CONNECTING",
198
+ Disconnecting: "DISCONNECTING",
199
+ Reconnecting: "RECONNECTING",
200
+ Running: "RUNNING",
201
+ NotRunning: "NOT_RUNNING",
202
+ Error: "ERROR"
203
+ };
204
+
97
205
  // src/tunnel_manager/TunnelManager.ts
98
206
  import path2 from "path";
99
207
  import { Worker } from "worker_threads";
@@ -268,6 +376,7 @@ function getLocalAddress(config) {
268
376
  // src/tunnel_manager/TunnelManager.ts
269
377
  var __filename3 = fileURLToPath2(import.meta.url);
270
378
  var __dirname3 = path2.dirname(__filename3);
379
+ var START_FAILED_MESSAGE = "Failed to start tunnel";
271
380
  function mapToSdkLogLevel(level) {
272
381
  if (level === "debug") return SdkLogLevel.DEBUG;
273
382
  if (level === "error") return SdkLogLevel.ERROR;
@@ -282,6 +391,13 @@ var TunnelAlreadyRunningError = class extends Error {
282
391
  this.name = "TunnelAlreadyRunningError";
283
392
  }
284
393
  };
394
+ var TunnelRebuildInProgressError = class extends Error {
395
+ constructor(tunnelId) {
396
+ super(`Tunnel "${tunnelId}" is already restarting`);
397
+ this.tunnelId = tunnelId;
398
+ this.name = "TunnelRebuildInProgressError";
399
+ }
400
+ };
285
401
  var TunnelManager = class _TunnelManager {
286
402
  constructor() {
287
403
  this.tunnelsByTunnelId = /* @__PURE__ */ new Map();
@@ -326,7 +442,7 @@ var TunnelManager = class _TunnelManager {
326
442
  throw new Error("configId is required and must be a non-empty string");
327
443
  }
328
444
  const existing = this.tunnelsByConfigId.get(configId);
329
- if (existing && !existing.isStopped) {
445
+ if (existing && (!existing.isStopped || existing.rebuild)) {
330
446
  throw new TunnelAlreadyRunningError(configId, existing.tunnelid);
331
447
  }
332
448
  return this._createTunnelWithProcessedConfig({
@@ -348,14 +464,15 @@ var TunnelManager = class _TunnelManager {
348
464
  * @private
349
465
  */
350
466
  async _createTunnelWithProcessedConfig(params) {
351
- const tunnelLogName = params.tunnelName || params.originalConfig?.name;
467
+ const tunnelConfig = { ...params.originalConfig, maxReconnectAttempts: 0 };
468
+ const tunnelLogName = params.tunnelName || tunnelConfig?.name;
352
469
  const tunnelLogPath = getTunnelLogPath(params.tunnelid, params.origin, tunnelLogName);
353
470
  maybeRotate(tunnelLogPath);
354
471
  attachTunnelLogger(params.tunnelid, params.origin, tunnelLogName);
355
472
  let instance;
356
473
  try {
357
- logger.debug("Creating tunnel instance with processed config", params.originalConfig);
358
- instance = await TunnelInstance.create(params.originalConfig, {
474
+ logger.debug("Creating tunnel instance with processed config", tunnelConfig);
475
+ instance = await TunnelInstance.create(tunnelConfig, {
359
476
  enabled: true,
360
477
  logLevel: mapToSdkLogLevel(getLogLevel()),
361
478
  logFilePath: tunnelLogPath,
@@ -373,7 +490,7 @@ var TunnelManager = class _TunnelManager {
373
490
  tunnelName: params.tunnelName,
374
491
  origin: params.origin,
375
492
  instance,
376
- tunnelConfig: params.originalConfig,
493
+ tunnelConfig,
377
494
  serve: params.serve,
378
495
  warnings: [],
379
496
  isStopped: false,
@@ -413,11 +530,12 @@ var TunnelManager = class _TunnelManager {
413
530
  try {
414
531
  urls = await managed.instance.start();
415
532
  } catch (error) {
416
- logger.warn("Failed to start tunnel", { tunnelId, error });
533
+ const reason = errorMessage(error) || START_FAILED_MESSAGE;
534
+ logger.warn("Failed to start tunnel", { tunnelId, error: reason });
417
535
  managed.isStopped = true;
418
536
  managed.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
419
537
  managed.lastError = {
420
- message: "Failed to start tunnel",
538
+ message: reason,
421
539
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
422
540
  isFatal: true
423
541
  };
@@ -588,6 +706,9 @@ var TunnelManager = class _TunnelManager {
588
706
  logger.error(`Tunnel "${tunnelId}" not found when fetching status`);
589
707
  throw new Error(`Tunnel "${tunnelId}" not found`);
590
708
  }
709
+ if (managed.rebuild?.startsAfter) {
710
+ return "starting" /* Starting */;
711
+ }
591
712
  if (managed.isStopped) {
592
713
  return "exited";
593
714
  }
@@ -638,6 +759,10 @@ var TunnelManager = class _TunnelManager {
638
759
  logger.warn("Attempted to remove tunnel that is not stopped", { tunnelId });
639
760
  return false;
640
761
  }
762
+ if (managed.rebuild) {
763
+ logger.warn("Attempted to remove tunnel that is restarting", { tunnelId });
764
+ return false;
765
+ }
641
766
  this._cleanupTunnelRecords(managed);
642
767
  logger.info("Removed stopped tunnel records", { tunnelId, configId: managed.configId });
643
768
  return true;
@@ -717,17 +842,27 @@ var TunnelManager = class _TunnelManager {
717
842
  if (!managed) {
718
843
  return Promise.reject(new Error(`Tunnel with configId "${configId}" not found`));
719
844
  }
720
- return Promise.resolve(managed.instance.getConfig());
845
+ return this.currentConfig(managed);
721
846
  }
722
847
  if (tunnelId) {
723
848
  const managed = this.tunnelsByTunnelId.get(tunnelId);
724
849
  if (!managed) {
725
850
  return Promise.reject(new Error(`Tunnel with tunnelId "${tunnelId}" not found`));
726
851
  }
727
- return Promise.resolve(managed.instance.getConfig());
852
+ return this.currentConfig(managed);
728
853
  }
729
854
  return Promise.reject(new Error(`Either configId or tunnelId must be provided`));
730
855
  }
856
+ /**
857
+ * The instance's config, asked of its worker. A stopped instance has let its worker go, so a stopped
858
+ * tunnel, including one a restart is replacing, answers with the config it was built from.
859
+ */
860
+ currentConfig(managed) {
861
+ if (managed.isStopped) {
862
+ return Promise.resolve(managed.tunnelConfig);
863
+ }
864
+ return Promise.resolve(managed.instance.getConfig());
865
+ }
731
866
  /**
732
867
  * Stop a tunnel instance and wait for the stop to settle, capped by a
733
868
  * timeout. Failure is logged, not thrown: the caller replaces the
@@ -751,6 +886,46 @@ var TunnelManager = class _TunnelManager {
751
886
  if (timer) clearTimeout(timer);
752
887
  }
753
888
  }
889
+ /**
890
+ * Marks the entry a restart or config update replaces. It stays in both maps until
891
+ * `_createTunnelWithProcessedConfig` sets the new entry under the same ids. When the new entry
892
+ * starts, the entry reports `starting`, and an error from an earlier run no longer describes it.
893
+ */
894
+ beginRebuild(managed, startsAfter) {
895
+ if (managed.rebuild) {
896
+ throw new TunnelRebuildInProgressError(managed.tunnelid);
897
+ }
898
+ managed.rebuild = { startsAfter };
899
+ if (startsAfter) {
900
+ managed.lastError = {};
901
+ }
902
+ }
903
+ /** Stops the instance being replaced, and marks it stopped so no reader calls it again. */
904
+ async stopForRebuild(managed) {
905
+ if (managed.isStopped) {
906
+ return;
907
+ }
908
+ await this.stopInstanceGuarded(managed);
909
+ managed.isStopped = true;
910
+ managed.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
911
+ }
912
+ /**
913
+ * Ends a failed rebuild. When the old entry is still in the maps, it stays listed as stopped with
914
+ * the reason. A new entry that failed to start already holds its own error.
915
+ */
916
+ failRebuild(managed, error) {
917
+ managed.rebuild = void 0;
918
+ if (this.tunnelsByTunnelId.get(managed.tunnelid) !== managed) {
919
+ return;
920
+ }
921
+ managed.isStopped = true;
922
+ managed.stoppedAt = managed.stoppedAt ?? (/* @__PURE__ */ new Date()).toISOString();
923
+ managed.lastError = {
924
+ message: errorMessage(error) || START_FAILED_MESSAGE,
925
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
926
+ isFatal: true
927
+ };
928
+ }
754
929
  /**
755
930
  * Restarts a tunnel with its current configuration.
756
931
  * This function will stop the tunnel if it's running and start it again.
@@ -761,6 +936,7 @@ var TunnelManager = class _TunnelManager {
761
936
  if (!existingTunnel) {
762
937
  throw new Error(`Tunnel "${tunnelid}" not found`);
763
938
  }
939
+ this.beginRebuild(existingTunnel, true);
764
940
  logger.info("Initiating tunnel restart", {
765
941
  tunnelId: tunnelid,
766
942
  configId: existingTunnel.configId
@@ -772,11 +948,7 @@ var TunnelManager = class _TunnelManager {
772
948
  const currentServe = existingTunnel.serve;
773
949
  const autoReconnect = existingTunnel.autoReconnect || false;
774
950
  const currentOrigin = existingTunnel.origin;
775
- if (!existingTunnel.isStopped) {
776
- await this.stopInstanceGuarded(existingTunnel);
777
- }
778
- this.tunnelsByTunnelId.delete(tunnelid);
779
- this.tunnelsByConfigId.delete(existingTunnel.configId);
951
+ await this.stopForRebuild(existingTunnel);
780
952
  this.tunnelStats.delete(tunnelid);
781
953
  this.tunnelStatsListeners.delete(tunnelid);
782
954
  this.tunnelErrorListeners.delete(tunnelid);
@@ -802,6 +974,7 @@ var TunnelManager = class _TunnelManager {
802
974
  }
803
975
  await this.startTunnel(newTunnel.tunnelid);
804
976
  } catch (error) {
977
+ this.failRebuild(existingTunnel, error);
805
978
  logger.error("Failed to restart tunnel", {
806
979
  tunnelid,
807
980
  error: error instanceof Error ? error.message : "Unknown error"
@@ -859,12 +1032,9 @@ var TunnelManager = class _TunnelManager {
859
1032
  const currentStartedAt = existingTunnel.startedAt;
860
1033
  const currentStoppedAt = existingTunnel.stoppedAt;
861
1034
  const requestedServe = this.resolveServePath(newConfig);
1035
+ this.beginRebuild(existingTunnel, !isStopped);
862
1036
  try {
863
- if (!isStopped) {
864
- await this.stopInstanceGuarded(existingTunnel);
865
- }
866
- this.tunnelsByTunnelId.delete(currentTunnelId);
867
- this.tunnelsByConfigId.delete(currentTunnelConfigId);
1037
+ await this.stopForRebuild(existingTunnel);
868
1038
  const mergedBaseConfig = {
869
1039
  ...newConfig,
870
1040
  configId,
@@ -925,6 +1095,7 @@ var TunnelManager = class _TunnelManager {
925
1095
  error: errorMessage(restoreError)
926
1096
  });
927
1097
  }
1098
+ this.failRebuild(existingTunnel, error);
928
1099
  throw error;
929
1100
  }
930
1101
  }
@@ -1738,11 +1909,23 @@ export {
1738
1909
  setTunnelLoggingEnabled,
1739
1910
  isTunnelLoggingEnabled,
1740
1911
  detachAllTunnelLoggers,
1912
+ TunnelStateType,
1913
+ TunnelErrorCodeType,
1914
+ TunnelWarningCode,
1915
+ ErrorCode,
1916
+ isErrorResponse,
1917
+ newErrorResponse,
1918
+ NewResponseObject,
1919
+ NewErrorResponseObject,
1920
+ newStatus,
1921
+ newStats,
1922
+ RemoteManagementStatus,
1741
1923
  getRandomId,
1742
1924
  isValidPort,
1743
1925
  errorMessage,
1744
1926
  getVersion,
1745
1927
  getLocalAddress,
1746
1928
  TunnelAlreadyRunningError,
1929
+ TunnelRebuildInProgressError,
1747
1930
  TunnelManager
1748
1931
  };