pinggy 0.6.1 → 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-JQAQRJPE.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({
@@ -414,11 +530,12 @@ var TunnelManager = class _TunnelManager {
414
530
  try {
415
531
  urls = await managed.instance.start();
416
532
  } catch (error) {
417
- 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 });
418
535
  managed.isStopped = true;
419
536
  managed.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
420
537
  managed.lastError = {
421
- message: "Failed to start tunnel",
538
+ message: reason,
422
539
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
423
540
  isFatal: true
424
541
  };
@@ -589,6 +706,9 @@ var TunnelManager = class _TunnelManager {
589
706
  logger.error(`Tunnel "${tunnelId}" not found when fetching status`);
590
707
  throw new Error(`Tunnel "${tunnelId}" not found`);
591
708
  }
709
+ if (managed.rebuild?.startsAfter) {
710
+ return "starting" /* Starting */;
711
+ }
592
712
  if (managed.isStopped) {
593
713
  return "exited";
594
714
  }
@@ -639,6 +759,10 @@ var TunnelManager = class _TunnelManager {
639
759
  logger.warn("Attempted to remove tunnel that is not stopped", { tunnelId });
640
760
  return false;
641
761
  }
762
+ if (managed.rebuild) {
763
+ logger.warn("Attempted to remove tunnel that is restarting", { tunnelId });
764
+ return false;
765
+ }
642
766
  this._cleanupTunnelRecords(managed);
643
767
  logger.info("Removed stopped tunnel records", { tunnelId, configId: managed.configId });
644
768
  return true;
@@ -718,17 +842,27 @@ var TunnelManager = class _TunnelManager {
718
842
  if (!managed) {
719
843
  return Promise.reject(new Error(`Tunnel with configId "${configId}" not found`));
720
844
  }
721
- return Promise.resolve(managed.instance.getConfig());
845
+ return this.currentConfig(managed);
722
846
  }
723
847
  if (tunnelId) {
724
848
  const managed = this.tunnelsByTunnelId.get(tunnelId);
725
849
  if (!managed) {
726
850
  return Promise.reject(new Error(`Tunnel with tunnelId "${tunnelId}" not found`));
727
851
  }
728
- return Promise.resolve(managed.instance.getConfig());
852
+ return this.currentConfig(managed);
729
853
  }
730
854
  return Promise.reject(new Error(`Either configId or tunnelId must be provided`));
731
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
+ }
732
866
  /**
733
867
  * Stop a tunnel instance and wait for the stop to settle, capped by a
734
868
  * timeout. Failure is logged, not thrown: the caller replaces the
@@ -752,6 +886,46 @@ var TunnelManager = class _TunnelManager {
752
886
  if (timer) clearTimeout(timer);
753
887
  }
754
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
+ }
755
929
  /**
756
930
  * Restarts a tunnel with its current configuration.
757
931
  * This function will stop the tunnel if it's running and start it again.
@@ -762,6 +936,7 @@ var TunnelManager = class _TunnelManager {
762
936
  if (!existingTunnel) {
763
937
  throw new Error(`Tunnel "${tunnelid}" not found`);
764
938
  }
939
+ this.beginRebuild(existingTunnel, true);
765
940
  logger.info("Initiating tunnel restart", {
766
941
  tunnelId: tunnelid,
767
942
  configId: existingTunnel.configId
@@ -773,11 +948,7 @@ var TunnelManager = class _TunnelManager {
773
948
  const currentServe = existingTunnel.serve;
774
949
  const autoReconnect = existingTunnel.autoReconnect || false;
775
950
  const currentOrigin = existingTunnel.origin;
776
- if (!existingTunnel.isStopped) {
777
- await this.stopInstanceGuarded(existingTunnel);
778
- }
779
- this.tunnelsByTunnelId.delete(tunnelid);
780
- this.tunnelsByConfigId.delete(existingTunnel.configId);
951
+ await this.stopForRebuild(existingTunnel);
781
952
  this.tunnelStats.delete(tunnelid);
782
953
  this.tunnelStatsListeners.delete(tunnelid);
783
954
  this.tunnelErrorListeners.delete(tunnelid);
@@ -803,6 +974,7 @@ var TunnelManager = class _TunnelManager {
803
974
  }
804
975
  await this.startTunnel(newTunnel.tunnelid);
805
976
  } catch (error) {
977
+ this.failRebuild(existingTunnel, error);
806
978
  logger.error("Failed to restart tunnel", {
807
979
  tunnelid,
808
980
  error: error instanceof Error ? error.message : "Unknown error"
@@ -860,12 +1032,9 @@ var TunnelManager = class _TunnelManager {
860
1032
  const currentStartedAt = existingTunnel.startedAt;
861
1033
  const currentStoppedAt = existingTunnel.stoppedAt;
862
1034
  const requestedServe = this.resolveServePath(newConfig);
1035
+ this.beginRebuild(existingTunnel, !isStopped);
863
1036
  try {
864
- if (!isStopped) {
865
- await this.stopInstanceGuarded(existingTunnel);
866
- }
867
- this.tunnelsByTunnelId.delete(currentTunnelId);
868
- this.tunnelsByConfigId.delete(currentTunnelConfigId);
1037
+ await this.stopForRebuild(existingTunnel);
869
1038
  const mergedBaseConfig = {
870
1039
  ...newConfig,
871
1040
  configId,
@@ -926,6 +1095,7 @@ var TunnelManager = class _TunnelManager {
926
1095
  error: errorMessage(restoreError)
927
1096
  });
928
1097
  }
1098
+ this.failRebuild(existingTunnel, error);
929
1099
  throw error;
930
1100
  }
931
1101
  }
@@ -1739,11 +1909,23 @@ export {
1739
1909
  setTunnelLoggingEnabled,
1740
1910
  isTunnelLoggingEnabled,
1741
1911
  detachAllTunnelLoggers,
1912
+ TunnelStateType,
1913
+ TunnelErrorCodeType,
1914
+ TunnelWarningCode,
1915
+ ErrorCode,
1916
+ isErrorResponse,
1917
+ newErrorResponse,
1918
+ NewResponseObject,
1919
+ NewErrorResponseObject,
1920
+ newStatus,
1921
+ newStats,
1922
+ RemoteManagementStatus,
1742
1923
  getRandomId,
1743
1924
  isValidPort,
1744
1925
  errorMessage,
1745
1926
  getVersion,
1746
1927
  getLocalAddress,
1747
1928
  TunnelAlreadyRunningError,
1929
+ TunnelRebuildInProgressError,
1748
1930
  TunnelManager
1749
1931
  };