pinggy 0.3.7 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/{chunk-65R2GMKQ.js → chunk-JD3U36U5.js} +726 -177
- package/dist/index.cjs +830 -247
- package/dist/index.d.cts +105 -75
- package/dist/index.d.ts +105 -75
- package/dist/index.js +2 -2
- package/dist/{main-2QDG7PWL.js → main-VIBOPJ64.js} +51 -32
- package/dist/workers/file_serve_worker.cjs +1181 -2
- package/dist/workers/file_serve_worker.js +1181 -2
- package/jest.config.cjs +13 -0
- package/package.json +5 -4
|
@@ -68,6 +68,9 @@ var _CLIPrinter = class _CLIPrinter {
|
|
|
68
68
|
console.error(pico2.red(pico2.bold("\u2716 Error:")), pico2.red(msg));
|
|
69
69
|
process.exit(1);
|
|
70
70
|
}
|
|
71
|
+
static red(message) {
|
|
72
|
+
return pico2.red(message);
|
|
73
|
+
}
|
|
71
74
|
static warn(message) {
|
|
72
75
|
console.warn(pico2.yellow(pico2.bold("\u26A0 Warning:")), pico2.yellow(message));
|
|
73
76
|
}
|
|
@@ -121,27 +124,36 @@ var CLIPrinter = _CLIPrinter;
|
|
|
121
124
|
var printer_default = CLIPrinter;
|
|
122
125
|
|
|
123
126
|
// src/utils/util.ts
|
|
124
|
-
import {
|
|
127
|
+
import { readFileSync } from "fs";
|
|
125
128
|
import { randomUUID } from "crypto";
|
|
129
|
+
import { fileURLToPath } from "url";
|
|
130
|
+
import { dirname, join } from "path";
|
|
126
131
|
function getRandomId() {
|
|
127
132
|
return randomUUID();
|
|
128
133
|
}
|
|
129
134
|
function isValidPort(p) {
|
|
130
135
|
return Number.isInteger(p) && p > 0 && p < 65536;
|
|
131
136
|
}
|
|
132
|
-
var
|
|
133
|
-
var
|
|
137
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
138
|
+
var __dirname2 = dirname(__filename2);
|
|
134
139
|
function getVersion() {
|
|
135
|
-
|
|
140
|
+
try {
|
|
141
|
+
const packageJsonPath = join(__dirname2, "../package.json");
|
|
142
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
143
|
+
return pkg.version ?? "";
|
|
144
|
+
} catch (error) {
|
|
145
|
+
printer_default.error("Error reading version info");
|
|
146
|
+
return "";
|
|
147
|
+
}
|
|
136
148
|
}
|
|
137
149
|
|
|
138
150
|
// src/tunnel_manager/TunnelManager.ts
|
|
139
151
|
import { pinggy } from "@pinggy/pinggy";
|
|
140
152
|
import path from "path";
|
|
141
153
|
import { Worker } from "worker_threads";
|
|
142
|
-
import { fileURLToPath } from "url";
|
|
143
|
-
var
|
|
144
|
-
var
|
|
154
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
155
|
+
var __filename3 = fileURLToPath2(import.meta.url);
|
|
156
|
+
var __dirname3 = path.dirname(__filename3);
|
|
145
157
|
var TunnelManager = class _TunnelManager {
|
|
146
158
|
constructor() {
|
|
147
159
|
this.tunnelsByTunnelId = /* @__PURE__ */ new Map();
|
|
@@ -149,6 +161,7 @@ var TunnelManager = class _TunnelManager {
|
|
|
149
161
|
this.tunnelStats = /* @__PURE__ */ new Map();
|
|
150
162
|
this.tunnelStatsListeners = /* @__PURE__ */ new Map();
|
|
151
163
|
this.tunnelErrorListeners = /* @__PURE__ */ new Map();
|
|
164
|
+
this.tunnelPollingErrorListeners = /* @__PURE__ */ new Map();
|
|
152
165
|
this.tunnelDisconnectListeners = /* @__PURE__ */ new Map();
|
|
153
166
|
this.tunnelWorkerErrorListeners = /* @__PURE__ */ new Map();
|
|
154
167
|
this.tunnelStartListeners = /* @__PURE__ */ new Map();
|
|
@@ -165,12 +178,9 @@ var TunnelManager = class _TunnelManager {
|
|
|
165
178
|
}
|
|
166
179
|
/**
|
|
167
180
|
* Creates a new managed tunnel instance with the given configuration.
|
|
168
|
-
*
|
|
181
|
+
* Optionally builds the config with forwarding rules based on buildConfig flag.
|
|
169
182
|
*
|
|
170
183
|
* @param config - The tunnel configuration options
|
|
171
|
-
* @param config.configid - Unique identifier for the tunnel configuration
|
|
172
|
-
* @param config.tunnelid - Optional custom tunnel identifier. If not provided, a random UUID will be generated
|
|
173
|
-
* @param config.additionalForwarding - Optional array of additional forwarding configurations
|
|
174
184
|
*
|
|
175
185
|
* @throws {Error} When configId is invalid or empty
|
|
176
186
|
* @throws {Error} When a tunnel with the given configId already exists
|
|
@@ -179,24 +189,19 @@ var TunnelManager = class _TunnelManager {
|
|
|
179
189
|
* status information, and statistics
|
|
180
190
|
*/
|
|
181
191
|
async createTunnel(config) {
|
|
182
|
-
const {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
throw new Error(`Tunnel with configId "${configid}" already exists`);
|
|
192
|
+
const { configId, tunnelid: requestedTunnelId, tunnelName, name, serve } = config;
|
|
193
|
+
const tunnelid = requestedTunnelId || getRandomId();
|
|
194
|
+
const autoReconnect = config.autoReconnect || false;
|
|
195
|
+
if (!configId || typeof configId !== "string" || configId.trim() === "") {
|
|
196
|
+
throw new Error("configId is required and must be a non-empty string");
|
|
188
197
|
}
|
|
189
|
-
const tunnelid = config.tunnelid || getRandomId();
|
|
190
|
-
const configWithForwarding = this.buildPinggyConfig(config, additionalForwarding);
|
|
191
198
|
return this._createTunnelWithProcessedConfig({
|
|
192
|
-
|
|
199
|
+
configId,
|
|
193
200
|
tunnelid,
|
|
194
|
-
tunnelName,
|
|
201
|
+
tunnelName: tunnelName || name,
|
|
195
202
|
originalConfig: config,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
serve: config.serve,
|
|
199
|
-
autoReconnect: config.autoReconnect !== void 0 ? config.autoReconnect : false
|
|
203
|
+
serve,
|
|
204
|
+
autoReconnect
|
|
200
205
|
});
|
|
201
206
|
}
|
|
202
207
|
/**
|
|
@@ -210,7 +215,8 @@ var TunnelManager = class _TunnelManager {
|
|
|
210
215
|
async _createTunnelWithProcessedConfig(params) {
|
|
211
216
|
let instance;
|
|
212
217
|
try {
|
|
213
|
-
instance
|
|
218
|
+
logger.debug("Creating tunnel instance with processed config", params.originalConfig);
|
|
219
|
+
instance = await pinggy.createTunnel(params.originalConfig);
|
|
214
220
|
} catch (e) {
|
|
215
221
|
logger.error("Error creating tunnel instance:", e);
|
|
216
222
|
throw e;
|
|
@@ -218,12 +224,10 @@ var TunnelManager = class _TunnelManager {
|
|
|
218
224
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
219
225
|
const managed = {
|
|
220
226
|
tunnelid: params.tunnelid,
|
|
221
|
-
|
|
227
|
+
configId: params.configId,
|
|
222
228
|
tunnelName: params.tunnelName,
|
|
223
229
|
instance,
|
|
224
230
|
tunnelConfig: params.originalConfig,
|
|
225
|
-
configWithForwarding: params.configWithForwarding,
|
|
226
|
-
additionalForwarding: params.additionalForwarding,
|
|
227
231
|
serve: params.serve,
|
|
228
232
|
warnings: [],
|
|
229
233
|
isStopped: false,
|
|
@@ -237,6 +241,7 @@ var TunnelManager = class _TunnelManager {
|
|
|
237
241
|
});
|
|
238
242
|
this.setupStatsCallback(params.tunnelid, managed);
|
|
239
243
|
this.setupErrorCallback(params.tunnelid, managed);
|
|
244
|
+
this.setupTunnelPollingErrorCallback(params.tunnelid, managed);
|
|
240
245
|
this.setupDisconnectCallback(params.tunnelid, managed);
|
|
241
246
|
this.setupWillReconnectCallback(params.tunnelid, managed);
|
|
242
247
|
this.setupReconnectingCallback(params.tunnelid, managed);
|
|
@@ -244,44 +249,10 @@ var TunnelManager = class _TunnelManager {
|
|
|
244
249
|
this.setupReconnectionFailedCallback(params.tunnelid, managed);
|
|
245
250
|
this.setUpTunnelWorkerErrorCallback(params.tunnelid, managed);
|
|
246
251
|
this.tunnelsByTunnelId.set(params.tunnelid, managed);
|
|
247
|
-
this.tunnelsByConfigId.set(params.
|
|
248
|
-
logger.info("Tunnel created", {
|
|
252
|
+
this.tunnelsByConfigId.set(params.configId, managed);
|
|
253
|
+
logger.info("Tunnel created", { configId: params.configId, tunnelId: params.tunnelid });
|
|
249
254
|
return managed;
|
|
250
255
|
}
|
|
251
|
-
/**
|
|
252
|
-
* Builds the Pinggy configuration by merging the default forwarding rule
|
|
253
|
-
* with additional forwarding rules from additionalForwarding array.
|
|
254
|
-
*
|
|
255
|
-
* @param config - The base Pinggy configuration
|
|
256
|
-
* @param additionalForwarding - Optional array of additional forwarding rules
|
|
257
|
-
* @returns Modified PinggyOptions
|
|
258
|
-
*/
|
|
259
|
-
buildPinggyConfig(config, additionalForwarding) {
|
|
260
|
-
const forwardingRules = [];
|
|
261
|
-
if (config.forwarding) {
|
|
262
|
-
forwardingRules.push({
|
|
263
|
-
type: config.tunnelType && config.tunnelType[0] || "http",
|
|
264
|
-
address: config.forwarding
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
if (Array.isArray(additionalForwarding) && additionalForwarding.length > 0) {
|
|
268
|
-
for (const rule of additionalForwarding) {
|
|
269
|
-
if (rule && rule.localDomain && rule.localPort && rule.remoteDomain && isValidPort(rule.localPort)) {
|
|
270
|
-
const forwardingRule = {
|
|
271
|
-
type: rule.protocol,
|
|
272
|
-
// In Future we can make this dynamic based on user input
|
|
273
|
-
address: `${rule.localDomain}:${rule.localPort}`,
|
|
274
|
-
listenAddress: rule.remotePort && isValidPort(rule.remotePort) ? `${rule.remoteDomain}:${rule.remotePort}` : rule.remoteDomain
|
|
275
|
-
};
|
|
276
|
-
forwardingRules.push(forwardingRule);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
return {
|
|
281
|
-
...config,
|
|
282
|
-
forwarding: forwardingRules.length > 0 ? forwardingRules : config.forwarding
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
256
|
/**
|
|
286
257
|
* Start a tunnel that was created but not yet started
|
|
287
258
|
*/
|
|
@@ -293,7 +264,7 @@ var TunnelManager = class _TunnelManager {
|
|
|
293
264
|
try {
|
|
294
265
|
urls = await managed.instance.start();
|
|
295
266
|
} catch (error) {
|
|
296
|
-
logger.
|
|
267
|
+
logger.warn("Failed to start tunnel", { tunnelId, error });
|
|
297
268
|
throw error;
|
|
298
269
|
}
|
|
299
270
|
logger.info("Tunnel started", { tunnelId, urls });
|
|
@@ -329,7 +300,7 @@ var TunnelManager = class _TunnelManager {
|
|
|
329
300
|
stopTunnel(tunnelId) {
|
|
330
301
|
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
331
302
|
if (!managed) throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
332
|
-
logger.info("Stopping tunnel", { tunnelId, configId: managed.
|
|
303
|
+
logger.info("Stopping tunnel", { tunnelId, configId: managed.configId });
|
|
333
304
|
try {
|
|
334
305
|
managed.instance.stop();
|
|
335
306
|
if (managed.serveWorker) {
|
|
@@ -341,6 +312,7 @@ var TunnelManager = class _TunnelManager {
|
|
|
341
312
|
this.tunnelStats.delete(tunnelId);
|
|
342
313
|
this.tunnelStatsListeners.delete(tunnelId);
|
|
343
314
|
this.tunnelErrorListeners.delete(tunnelId);
|
|
315
|
+
this.tunnelPollingErrorListeners.delete(tunnelId);
|
|
344
316
|
this.tunnelDisconnectListeners.delete(tunnelId);
|
|
345
317
|
this.tunnelWorkerErrorListeners.delete(tunnelId);
|
|
346
318
|
this.tunnelStartListeners.delete(tunnelId);
|
|
@@ -352,8 +324,8 @@ var TunnelManager = class _TunnelManager {
|
|
|
352
324
|
managed.warnings = managed.warnings ?? [];
|
|
353
325
|
managed.isStopped = true;
|
|
354
326
|
managed.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
355
|
-
logger.info("Tunnel stopped", { tunnelId, configId: managed.
|
|
356
|
-
return {
|
|
327
|
+
logger.info("Tunnel stopped", { tunnelId, configId: managed.configId });
|
|
328
|
+
return { configId: managed.configId, tunnelid: managed.tunnelid };
|
|
357
329
|
} catch (error) {
|
|
358
330
|
logger.error("Failed to stop tunnel", { tunnelId, error });
|
|
359
331
|
throw error;
|
|
@@ -386,11 +358,10 @@ var TunnelManager = class _TunnelManager {
|
|
|
386
358
|
const tunnelList = await Promise.all(Array.from(this.tunnelsByTunnelId.values()).map(async (tunnel) => {
|
|
387
359
|
return {
|
|
388
360
|
tunnelid: tunnel.tunnelid,
|
|
389
|
-
|
|
361
|
+
configId: tunnel.configId,
|
|
390
362
|
tunnelName: tunnel.tunnelName,
|
|
391
363
|
tunnelConfig: tunnel.tunnelConfig,
|
|
392
364
|
remoteurls: !tunnel.isStopped ? await this.getTunnelUrls(tunnel.tunnelid) : [],
|
|
393
|
-
additionalForwarding: tunnel.additionalForwarding,
|
|
394
365
|
serve: tunnel.serve
|
|
395
366
|
};
|
|
396
367
|
}));
|
|
@@ -431,6 +402,15 @@ var TunnelManager = class _TunnelManager {
|
|
|
431
402
|
this.tunnelsByConfigId.clear();
|
|
432
403
|
this.tunnelStats.clear();
|
|
433
404
|
this.tunnelStatsListeners.clear();
|
|
405
|
+
this.tunnelErrorListeners.clear();
|
|
406
|
+
this.tunnelPollingErrorListeners.clear();
|
|
407
|
+
this.tunnelDisconnectListeners.clear();
|
|
408
|
+
this.tunnelWorkerErrorListeners.clear();
|
|
409
|
+
this.tunnelStartListeners.clear();
|
|
410
|
+
this.tunnelWillReconnectListeners.clear();
|
|
411
|
+
this.tunnelReconnectingListeners.clear();
|
|
412
|
+
this.tunnelReconnectionCompletedListeners.clear();
|
|
413
|
+
this.tunnelReconnectionFailedListeners.clear();
|
|
434
414
|
logger.info("All tunnels stopped and cleared");
|
|
435
415
|
}
|
|
436
416
|
/**
|
|
@@ -451,7 +431,7 @@ var TunnelManager = class _TunnelManager {
|
|
|
451
431
|
return false;
|
|
452
432
|
}
|
|
453
433
|
this._cleanupTunnelRecords(managed);
|
|
454
|
-
logger.info("Removed stopped tunnel records", { tunnelId, configId: managed.
|
|
434
|
+
logger.info("Removed stopped tunnel records", { tunnelId, configId: managed.configId });
|
|
455
435
|
return true;
|
|
456
436
|
}
|
|
457
437
|
/**
|
|
@@ -478,6 +458,7 @@ var TunnelManager = class _TunnelManager {
|
|
|
478
458
|
this.tunnelStats.delete(managed.tunnelid);
|
|
479
459
|
this.tunnelStatsListeners.delete(managed.tunnelid);
|
|
480
460
|
this.tunnelErrorListeners.delete(managed.tunnelid);
|
|
461
|
+
this.tunnelPollingErrorListeners.delete(managed.tunnelid);
|
|
481
462
|
this.tunnelDisconnectListeners.delete(managed.tunnelid);
|
|
482
463
|
this.tunnelWorkerErrorListeners.delete(managed.tunnelid);
|
|
483
464
|
this.tunnelStartListeners.delete(managed.tunnelid);
|
|
@@ -486,7 +467,7 @@ var TunnelManager = class _TunnelManager {
|
|
|
486
467
|
this.tunnelReconnectionCompletedListeners.delete(managed.tunnelid);
|
|
487
468
|
this.tunnelReconnectionFailedListeners.delete(managed.tunnelid);
|
|
488
469
|
this.tunnelsByTunnelId.delete(managed.tunnelid);
|
|
489
|
-
this.tunnelsByConfigId.delete(managed.
|
|
470
|
+
this.tunnelsByConfigId.delete(managed.configId);
|
|
490
471
|
} catch (e) {
|
|
491
472
|
logger.warn("Failed cleaning up tunnel records", { tunnelId: managed.tunnelid, error: e });
|
|
492
473
|
}
|
|
@@ -547,21 +528,20 @@ var TunnelManager = class _TunnelManager {
|
|
|
547
528
|
}
|
|
548
529
|
logger.info("Initiating tunnel restart", {
|
|
549
530
|
tunnelId: tunnelid,
|
|
550
|
-
configId: existingTunnel.
|
|
531
|
+
configId: existingTunnel.configId
|
|
551
532
|
});
|
|
552
533
|
try {
|
|
553
534
|
const tunnelName = existingTunnel.tunnelName;
|
|
554
|
-
const currentConfigId = existingTunnel.
|
|
535
|
+
const currentConfigId = existingTunnel.configId;
|
|
555
536
|
const currentConfig = existingTunnel.tunnelConfig;
|
|
556
|
-
const configWithForwarding = existingTunnel.configWithForwarding;
|
|
557
|
-
const additionalForwarding = existingTunnel.additionalForwarding;
|
|
558
537
|
const currentServe = existingTunnel.serve;
|
|
559
538
|
const autoReconnect = existingTunnel.autoReconnect || false;
|
|
560
539
|
this.tunnelsByTunnelId.delete(tunnelid);
|
|
561
|
-
this.tunnelsByConfigId.delete(existingTunnel.
|
|
540
|
+
this.tunnelsByConfigId.delete(existingTunnel.configId);
|
|
562
541
|
this.tunnelStats.delete(tunnelid);
|
|
563
542
|
this.tunnelStatsListeners.delete(tunnelid);
|
|
564
543
|
this.tunnelErrorListeners.delete(tunnelid);
|
|
544
|
+
this.tunnelPollingErrorListeners.delete(tunnelid);
|
|
565
545
|
this.tunnelDisconnectListeners.delete(tunnelid);
|
|
566
546
|
this.tunnelWorkerErrorListeners.delete(tunnelid);
|
|
567
547
|
this.tunnelStartListeners.delete(tunnelid);
|
|
@@ -570,12 +550,10 @@ var TunnelManager = class _TunnelManager {
|
|
|
570
550
|
this.tunnelReconnectionCompletedListeners.delete(tunnelid);
|
|
571
551
|
this.tunnelReconnectionFailedListeners.delete(tunnelid);
|
|
572
552
|
const newTunnel = await this._createTunnelWithProcessedConfig({
|
|
573
|
-
|
|
553
|
+
configId: currentConfigId,
|
|
574
554
|
tunnelid,
|
|
575
555
|
tunnelName,
|
|
576
556
|
originalConfig: currentConfig,
|
|
577
|
-
configWithForwarding,
|
|
578
|
-
additionalForwarding,
|
|
579
557
|
serve: currentServe,
|
|
580
558
|
autoReconnect
|
|
581
559
|
});
|
|
@@ -604,20 +582,18 @@ var TunnelManager = class _TunnelManager {
|
|
|
604
582
|
* @throws Error if the tunnel is not found or if the update process fails
|
|
605
583
|
*/
|
|
606
584
|
async updateConfig(newConfig) {
|
|
607
|
-
const {
|
|
608
|
-
if (!
|
|
609
|
-
throw new Error(`Invalid
|
|
585
|
+
const { configId, tunnelName: newTunnelName } = newConfig;
|
|
586
|
+
if (!configId || configId.trim().length === 0) {
|
|
587
|
+
throw new Error(`Invalid configId: "${configId}"`);
|
|
610
588
|
}
|
|
611
|
-
const existingTunnel = this.tunnelsByConfigId.get(
|
|
589
|
+
const existingTunnel = this.tunnelsByConfigId.get(configId);
|
|
612
590
|
if (!existingTunnel) {
|
|
613
|
-
throw new Error(`Tunnel with config id "${
|
|
591
|
+
throw new Error(`Tunnel with config id "${configId}" not found`);
|
|
614
592
|
}
|
|
615
593
|
const isStopped = existingTunnel.isStopped;
|
|
616
594
|
const currentTunnelConfig = existingTunnel.tunnelConfig;
|
|
617
|
-
const currentConfigWithForwarding = existingTunnel.configWithForwarding;
|
|
618
595
|
const currentTunnelId = existingTunnel.tunnelid;
|
|
619
|
-
const currentTunnelConfigId = existingTunnel.
|
|
620
|
-
const currentAdditionalForwarding = existingTunnel.additionalForwarding;
|
|
596
|
+
const currentTunnelConfigId = existingTunnel.configId;
|
|
621
597
|
const currentTunnelName = existingTunnel.tunnelName;
|
|
622
598
|
const currentServe = existingTunnel.serve;
|
|
623
599
|
const currentAutoReconnect = existingTunnel.autoReconnect || false;
|
|
@@ -629,22 +605,19 @@ var TunnelManager = class _TunnelManager {
|
|
|
629
605
|
this.tunnelsByConfigId.delete(currentTunnelConfigId);
|
|
630
606
|
const mergedBaseConfig = {
|
|
631
607
|
...newConfig,
|
|
632
|
-
|
|
608
|
+
configId,
|
|
633
609
|
tunnelName: newTunnelName !== void 0 ? newTunnelName : currentTunnelName,
|
|
634
610
|
serve: newConfig.serve !== void 0 ? newConfig.serve : currentServe
|
|
635
611
|
};
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
);
|
|
612
|
+
const effectiveServe = newConfig.serve !== void 0 ? newConfig.serve : currentServe;
|
|
613
|
+
const effectiveTunnelName = newTunnelName !== void 0 ? newTunnelName : currentTunnelName;
|
|
614
|
+
let configWithForwarding;
|
|
640
615
|
const newTunnel = await this._createTunnelWithProcessedConfig({
|
|
641
|
-
|
|
616
|
+
configId,
|
|
642
617
|
tunnelid: currentTunnelId,
|
|
643
|
-
tunnelName:
|
|
618
|
+
tunnelName: effectiveTunnelName,
|
|
644
619
|
originalConfig: mergedBaseConfig,
|
|
645
|
-
|
|
646
|
-
additionalForwarding: additionalForwarding !== void 0 ? additionalForwarding : currentAdditionalForwarding,
|
|
647
|
-
serve: newConfig.serve !== void 0 ? newConfig.serve : currentServe,
|
|
620
|
+
serve: effectiveServe,
|
|
648
621
|
autoReconnect: currentAutoReconnect
|
|
649
622
|
});
|
|
650
623
|
if (!isStopped) {
|
|
@@ -652,23 +625,21 @@ var TunnelManager = class _TunnelManager {
|
|
|
652
625
|
}
|
|
653
626
|
logger.info("Tunnel configuration updated", {
|
|
654
627
|
tunnelId: newTunnel.tunnelid,
|
|
655
|
-
configId: newTunnel.
|
|
628
|
+
configId: newTunnel.configId,
|
|
656
629
|
isStopped
|
|
657
630
|
});
|
|
658
631
|
return newTunnel;
|
|
659
632
|
} catch (error) {
|
|
660
633
|
logger.error("Error updating tunnel configuration", {
|
|
661
|
-
configId
|
|
634
|
+
configId,
|
|
662
635
|
error: error instanceof Error ? error.message : String(error)
|
|
663
636
|
});
|
|
664
637
|
try {
|
|
665
638
|
const originalTunnel = await this._createTunnelWithProcessedConfig({
|
|
666
|
-
|
|
639
|
+
configId: currentTunnelConfigId,
|
|
667
640
|
tunnelid: currentTunnelId,
|
|
668
641
|
tunnelName: currentTunnelName,
|
|
669
642
|
originalConfig: currentTunnelConfig,
|
|
670
|
-
configWithForwarding: currentConfigWithForwarding,
|
|
671
|
-
additionalForwarding: currentAdditionalForwarding,
|
|
672
643
|
serve: currentServe,
|
|
673
644
|
autoReconnect: currentAutoReconnect
|
|
674
645
|
});
|
|
@@ -784,6 +755,19 @@ var TunnelManager = class _TunnelManager {
|
|
|
784
755
|
logger.info("Error listener registered for tunnel", { tunnelId, listenerId });
|
|
785
756
|
return listenerId;
|
|
786
757
|
}
|
|
758
|
+
async registerPollingErrorListener(tunnelId, listener) {
|
|
759
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
760
|
+
if (!managed) {
|
|
761
|
+
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
762
|
+
}
|
|
763
|
+
if (!this.tunnelPollingErrorListeners.has(tunnelId)) {
|
|
764
|
+
this.tunnelPollingErrorListeners.set(tunnelId, /* @__PURE__ */ new Map());
|
|
765
|
+
}
|
|
766
|
+
const listenerId = getRandomId();
|
|
767
|
+
this.tunnelPollingErrorListeners.get(tunnelId).set(listenerId, listener);
|
|
768
|
+
logger.info("Polling error listener registered for tunnel", { tunnelId, listenerId });
|
|
769
|
+
return listenerId;
|
|
770
|
+
}
|
|
787
771
|
async registerDisconnectListener(tunnelId, listener) {
|
|
788
772
|
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
789
773
|
if (!managed) {
|
|
@@ -915,6 +899,22 @@ var TunnelManager = class _TunnelManager {
|
|
|
915
899
|
logger.warn("Attempted to deregister non-existent error listener", { tunnelId, listenerId });
|
|
916
900
|
}
|
|
917
901
|
}
|
|
902
|
+
deregisterPollingErrorListener(tunnelId, listenerId) {
|
|
903
|
+
const listeners = this.tunnelPollingErrorListeners.get(tunnelId);
|
|
904
|
+
if (!listeners) {
|
|
905
|
+
logger.warn("No polling error listeners found for tunnel", { tunnelId });
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
const removed = listeners.delete(listenerId);
|
|
909
|
+
if (removed) {
|
|
910
|
+
logger.info("Polling error listener deregistered", { tunnelId, listenerId });
|
|
911
|
+
if (listeners.size === 0) {
|
|
912
|
+
this.tunnelPollingErrorListeners.delete(tunnelId);
|
|
913
|
+
}
|
|
914
|
+
} else {
|
|
915
|
+
logger.warn("Attempted to deregister non-existent polling error listener", { tunnelId, listenerId });
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
918
|
deregisterDisconnectListener(tunnelId, listenerId) {
|
|
919
919
|
const listeners = this.tunnelDisconnectListeners.get(tunnelId);
|
|
920
920
|
if (!listeners) {
|
|
@@ -1032,6 +1032,38 @@ var TunnelManager = class _TunnelManager {
|
|
|
1032
1032
|
logger.warn("Failed to set up stats callback", { tunnelId, error });
|
|
1033
1033
|
}
|
|
1034
1034
|
}
|
|
1035
|
+
setupTunnelPollingErrorCallback(tunnelId, managed) {
|
|
1036
|
+
try {
|
|
1037
|
+
const callback = ({ error }) => {
|
|
1038
|
+
try {
|
|
1039
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1040
|
+
logger.info("Tunnel reported polling error", { tunnelId, errorMessage });
|
|
1041
|
+
this.notifyPollingErrorListeners(tunnelId, errorMessage);
|
|
1042
|
+
} catch (e) {
|
|
1043
|
+
logger.warn("Error handling tunnel polling error callback", { tunnelId, e });
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
managed.instance.setPollingErrorCallback(callback);
|
|
1047
|
+
logger.debug("Tunnel polling error callback set up for tunnel", { tunnelId });
|
|
1048
|
+
} catch (error) {
|
|
1049
|
+
logger.warn("Failed to set up tunnel polling error callback", { tunnelId, error });
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
notifyPollingErrorListeners(tunnelId, errorMsg) {
|
|
1053
|
+
try {
|
|
1054
|
+
const listeners = this.tunnelPollingErrorListeners.get(tunnelId);
|
|
1055
|
+
if (!listeners) return;
|
|
1056
|
+
for (const [id, listener] of listeners) {
|
|
1057
|
+
try {
|
|
1058
|
+
listener(tunnelId, errorMsg);
|
|
1059
|
+
} catch (err) {
|
|
1060
|
+
logger.debug("Error in polling-error-listener callback", { listenerId: id, tunnelId, err });
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
} catch (err) {
|
|
1064
|
+
logger.debug("Failed to notify polling error listeners", { tunnelId, err });
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1035
1067
|
notifyErrorListeners(tunnelId, errorMsg, isFatal) {
|
|
1036
1068
|
try {
|
|
1037
1069
|
const listeners = this.tunnelErrorListeners.get(tunnelId);
|
|
@@ -1304,9 +1336,9 @@ var TunnelManager = class _TunnelManager {
|
|
|
1304
1336
|
}
|
|
1305
1337
|
startStaticFileServer(managed) {
|
|
1306
1338
|
try {
|
|
1307
|
-
const
|
|
1308
|
-
const
|
|
1309
|
-
const fileServerWorkerPath = path.join(
|
|
1339
|
+
const __filename4 = fileURLToPath2(import.meta.url);
|
|
1340
|
+
const __dirname4 = path.dirname(__filename4);
|
|
1341
|
+
const fileServerWorkerPath = path.join(__dirname4, "workers", "file_serve_worker.cjs");
|
|
1310
1342
|
const staticServerWorker = new Worker(fileServerWorkerPath, {
|
|
1311
1343
|
workerData: {
|
|
1312
1344
|
dir: managed.serve,
|
|
@@ -1449,11 +1481,11 @@ var RemoteManagementStatus = {
|
|
|
1449
1481
|
};
|
|
1450
1482
|
|
|
1451
1483
|
// src/remote_management/remote_schema.ts
|
|
1452
|
-
import { TunnelType
|
|
1484
|
+
import { TunnelType } from "@pinggy/pinggy";
|
|
1453
1485
|
import { z } from "zod";
|
|
1454
1486
|
var HeaderModificationSchema = z.object({
|
|
1455
1487
|
key: z.string(),
|
|
1456
|
-
value: z.array(z.string()).optional(),
|
|
1488
|
+
value: z.array(z.string()).nullable().optional(),
|
|
1457
1489
|
type: z.enum(["add", "remove", "update"])
|
|
1458
1490
|
});
|
|
1459
1491
|
var AdditionalForwardingSchema = z.object({
|
|
@@ -1469,7 +1501,7 @@ var TunnelConfigSchema = z.object({
|
|
|
1469
1501
|
// legacy key
|
|
1470
1502
|
autoreconnect: z.boolean(),
|
|
1471
1503
|
basicauth: z.array(z.object({ username: z.string(), password: z.string() })).nullable(),
|
|
1472
|
-
bearerauth: z.string().nullable(),
|
|
1504
|
+
bearerauth: z.array(z.string()).nullable(),
|
|
1473
1505
|
configid: z.string(),
|
|
1474
1506
|
configname: z.string(),
|
|
1475
1507
|
greetmsg: z.string().optional(),
|
|
@@ -1491,11 +1523,11 @@ var TunnelConfigSchema = z.object({
|
|
|
1491
1523
|
token: z.string(),
|
|
1492
1524
|
tunnelTimeout: z.number(),
|
|
1493
1525
|
type: z.enum([
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1526
|
+
TunnelType.Http,
|
|
1527
|
+
TunnelType.Tcp,
|
|
1528
|
+
TunnelType.Udp,
|
|
1529
|
+
TunnelType.Tls,
|
|
1530
|
+
TunnelType.TlsTcp
|
|
1499
1531
|
]),
|
|
1500
1532
|
webdebuggerport: z.number(),
|
|
1501
1533
|
xff: z.string(),
|
|
@@ -1526,15 +1558,102 @@ var RestartSchema = StopSchema;
|
|
|
1526
1558
|
var UpdateConfigSchema = z.object({
|
|
1527
1559
|
tunnelConfig: TunnelConfigSchema
|
|
1528
1560
|
});
|
|
1561
|
+
var ForwardingEntryV2Schema = z.object({
|
|
1562
|
+
listenAddress: z.string().optional(),
|
|
1563
|
+
address: z.string(),
|
|
1564
|
+
type: z.enum([TunnelType.Http, TunnelType.Tcp, TunnelType.Udp, TunnelType.Tls, TunnelType.TlsTcp]).optional()
|
|
1565
|
+
});
|
|
1566
|
+
var TunnelConfigV1Schema = z.object({
|
|
1567
|
+
// Meta Info
|
|
1568
|
+
version: z.string(),
|
|
1569
|
+
name: z.string(),
|
|
1570
|
+
configId: z.string(),
|
|
1571
|
+
// General tunnel configurations
|
|
1572
|
+
serverAddress: z.string().optional(),
|
|
1573
|
+
token: z.string().optional(),
|
|
1574
|
+
autoReconnect: z.boolean().optional(),
|
|
1575
|
+
reconnectInterval: z.number().optional(),
|
|
1576
|
+
maxReconnectAttempts: z.number().optional(),
|
|
1577
|
+
force: z.boolean(),
|
|
1578
|
+
keepAliveInterval: z.number().optional(),
|
|
1579
|
+
webDebugger: z.string(),
|
|
1580
|
+
//Forwarding
|
|
1581
|
+
// Either a URL string (e.g. "https://localhost:5555") or an array of forwarding entries.
|
|
1582
|
+
forwarding: z.union([
|
|
1583
|
+
z.string(),
|
|
1584
|
+
z.array(ForwardingEntryV2Schema)
|
|
1585
|
+
]),
|
|
1586
|
+
// IP whitelist
|
|
1587
|
+
ipWhitelist: z.array(z.string()).optional(),
|
|
1588
|
+
basicAuth: z.array(z.object({ username: z.string(), password: z.string() })).optional(),
|
|
1589
|
+
bearerTokenAuth: z.array(z.string()).optional(),
|
|
1590
|
+
headerModification: z.array(HeaderModificationSchema).optional(),
|
|
1591
|
+
reverseProxy: z.boolean().optional(),
|
|
1592
|
+
xForwardedFor: z.boolean().optional(),
|
|
1593
|
+
httpsOnly: z.boolean().optional(),
|
|
1594
|
+
originalRequestUrl: z.boolean().optional(),
|
|
1595
|
+
allowPreflight: z.boolean().optional(),
|
|
1596
|
+
serve: z.string().optional(),
|
|
1597
|
+
optional: z.record(z.string(), z.unknown()).optional()
|
|
1598
|
+
});
|
|
1599
|
+
var StartV2Schema = z.object({
|
|
1600
|
+
tunnelID: z.string().nullable().optional(),
|
|
1601
|
+
tunnelConfig: TunnelConfigV1Schema
|
|
1602
|
+
});
|
|
1603
|
+
var UpdateConfigV2Schema = z.object({
|
|
1604
|
+
tunnelConfig: TunnelConfigV1Schema
|
|
1605
|
+
});
|
|
1606
|
+
function pinggyOptionsToTunnelConfigV1(opts, configStoredInCli) {
|
|
1607
|
+
const parsedTokens = opts.bearerTokenAuth ? Array.isArray(opts.bearerTokenAuth) ? opts.bearerTokenAuth : JSON.parse(opts.bearerTokenAuth) : [];
|
|
1608
|
+
return {
|
|
1609
|
+
version: configStoredInCli.version || "1.0",
|
|
1610
|
+
name: configStoredInCli.name || "",
|
|
1611
|
+
configId: configStoredInCli.configId || "",
|
|
1612
|
+
serverAddress: opts.serverAddress || "a.pinggy.io:443",
|
|
1613
|
+
token: opts.token || "",
|
|
1614
|
+
autoReconnect: opts.autoReconnect ?? true,
|
|
1615
|
+
force: opts.force ?? false,
|
|
1616
|
+
webDebugger: opts.webDebugger || "",
|
|
1617
|
+
forwarding: opts.forwarding ? opts.forwarding : "",
|
|
1618
|
+
ipWhitelist: opts.ipWhitelist ? Array.isArray(opts.ipWhitelist) ? opts.ipWhitelist : JSON.parse(opts.ipWhitelist) : [],
|
|
1619
|
+
basicAuth: opts.basicAuth && Object.keys(opts.basicAuth).length ? opts.basicAuth : void 0,
|
|
1620
|
+
bearerTokenAuth: parsedTokens.length ? parsedTokens : void 0,
|
|
1621
|
+
headerModification: opts.headerModification || [],
|
|
1622
|
+
reverseProxy: opts.reverseProxy ?? false,
|
|
1623
|
+
xForwardedFor: !!opts.xForwardedFor,
|
|
1624
|
+
httpsOnly: opts.httpsOnly ?? false,
|
|
1625
|
+
originalRequestUrl: opts.originalRequestUrl ?? false,
|
|
1626
|
+
allowPreflight: opts.allowPreflight ?? false,
|
|
1627
|
+
optional: opts.optional || {}
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1529
1630
|
function tunnelConfigToPinggyOptions(config) {
|
|
1631
|
+
const forwardingData = [];
|
|
1632
|
+
forwardingData.push({
|
|
1633
|
+
address: `${config.forwardedhost}:${config.localport}`,
|
|
1634
|
+
type: config.type || TunnelType.Http
|
|
1635
|
+
// Default to HTTP for the primary forwarding entry
|
|
1636
|
+
});
|
|
1637
|
+
if (config.additionalForwarding && Array.isArray(config.additionalForwarding)) {
|
|
1638
|
+
config.additionalForwarding.forEach((entry) => {
|
|
1639
|
+
if (entry.localDomain && entry.localPort && entry.remoteDomain) {
|
|
1640
|
+
const listenAddress = entry.remotePort && isValidPort(entry.remotePort) ? `${entry.remoteDomain}:${entry.remotePort}` : entry.remoteDomain;
|
|
1641
|
+
forwardingData.push({
|
|
1642
|
+
address: `${entry.localDomain}:${entry.localPort}`,
|
|
1643
|
+
listenAddress,
|
|
1644
|
+
type: TunnelType.Http
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1530
1649
|
return {
|
|
1531
1650
|
token: config.token || "",
|
|
1532
1651
|
serverAddress: config.serveraddress || "free.pinggy.io",
|
|
1533
|
-
forwarding:
|
|
1652
|
+
forwarding: forwardingData,
|
|
1534
1653
|
webDebugger: config.webdebuggerport ? `localhost:${config.webdebuggerport}` : "",
|
|
1535
1654
|
ipWhitelist: config.ipwhitelist || [],
|
|
1536
1655
|
basicAuth: config.basicauth ? config.basicauth : [],
|
|
1537
|
-
bearerTokenAuth: config.bearerauth
|
|
1656
|
+
bearerTokenAuth: config.bearerauth || [],
|
|
1538
1657
|
headerModification: config.headermodification,
|
|
1539
1658
|
xForwardedFor: !!config.xff,
|
|
1540
1659
|
httpsOnly: config.httpsOnly,
|
|
@@ -1548,18 +1667,38 @@ function tunnelConfigToPinggyOptions(config) {
|
|
|
1548
1667
|
}
|
|
1549
1668
|
};
|
|
1550
1669
|
}
|
|
1551
|
-
function pinggyOptionsToTunnelConfig(opts, configid, configName, localserverTls, greetMsg,
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1670
|
+
function pinggyOptionsToTunnelConfig(opts, configid, configName, localserverTls, greetMsg, serve) {
|
|
1671
|
+
let primaryEntry;
|
|
1672
|
+
let additionalEntries = [];
|
|
1673
|
+
if (Array.isArray(opts.forwarding)) {
|
|
1674
|
+
primaryEntry = opts.forwarding.find((e) => !e.listenAddress) ?? opts.forwarding[0];
|
|
1675
|
+
additionalEntries = opts.forwarding.filter(
|
|
1676
|
+
(e) => e !== primaryEntry && Boolean(e.listenAddress)
|
|
1677
|
+
);
|
|
1678
|
+
}
|
|
1679
|
+
const forwarding = primaryEntry ? String(primaryEntry.address) : String(opts.forwarding);
|
|
1680
|
+
const [parsedForwardedHost, portStr] = forwarding.split(":");
|
|
1681
|
+
const parsedLocalPort = parseInt(portStr, 10);
|
|
1682
|
+
const tunnelType = primaryEntry?.type ?? TunnelType.Http;
|
|
1683
|
+
const additionalForwarding = additionalEntries.map((e) => {
|
|
1684
|
+
const [localDomain, localPortStr] = String(e.address).split(":");
|
|
1685
|
+
const [remoteDomain, remotePortStr] = String(e.listenAddress).split(":");
|
|
1686
|
+
const localPort = parseInt(localPortStr, 10);
|
|
1687
|
+
const remotePort = parseInt(remotePortStr, 10);
|
|
1688
|
+
return {
|
|
1689
|
+
localDomain,
|
|
1690
|
+
localPort: isNaN(localPort) ? 0 : localPort,
|
|
1691
|
+
remoteDomain,
|
|
1692
|
+
remotePort: isNaN(remotePort) ? 0 : remotePort
|
|
1693
|
+
};
|
|
1694
|
+
});
|
|
1556
1695
|
const parsedTokens = opts.bearerTokenAuth ? Array.isArray(opts.bearerTokenAuth) ? opts.bearerTokenAuth : JSON.parse(opts.bearerTokenAuth) : [];
|
|
1557
1696
|
return {
|
|
1558
1697
|
allowPreflight: opts.allowPreflight ?? false,
|
|
1559
1698
|
allowpreflight: opts.allowPreflight ?? false,
|
|
1560
1699
|
autoreconnect: opts.autoReconnect ?? false,
|
|
1561
1700
|
basicauth: opts.basicAuth && Object.keys(opts.basicAuth).length ? opts.basicAuth : null,
|
|
1562
|
-
bearerauth: parsedTokens.length ? parsedTokens.join(",") : null,
|
|
1701
|
+
bearerauth: parsedTokens.length ? [parsedTokens.join(",")] : null,
|
|
1563
1702
|
configid,
|
|
1564
1703
|
configname: configName,
|
|
1565
1704
|
greetmsg: greetMsg || "",
|
|
@@ -1590,7 +1729,6 @@ function pinggyOptionsToTunnelConfig(opts, configid, configName, localserverTls,
|
|
|
1590
1729
|
}
|
|
1591
1730
|
|
|
1592
1731
|
// src/remote_management/handler.ts
|
|
1593
|
-
import { TunnelType as TunnelType3 } from "@pinggy/pinggy";
|
|
1594
1732
|
var TunnelOperations = class {
|
|
1595
1733
|
constructor() {
|
|
1596
1734
|
this.tunnelManager = TunnelManager.getInstance();
|
|
@@ -1609,7 +1747,7 @@ var TunnelOperations = class {
|
|
|
1609
1747
|
return status;
|
|
1610
1748
|
}
|
|
1611
1749
|
// --- Helper to construct TunnelResponse ---
|
|
1612
|
-
async buildTunnelResponse(tunnelid, tunnelConfig, configid, tunnelName,
|
|
1750
|
+
async buildTunnelResponse(tunnelid, tunnelConfig, configid, tunnelName, serve) {
|
|
1613
1751
|
const [status, stats, tlsInfo, greetMsg, remoteurls] = await Promise.all([
|
|
1614
1752
|
this.tunnelManager.getTunnelStatus(tunnelid),
|
|
1615
1753
|
this.tunnelManager.getLatestTunnelStats(tunnelid) || newStats(),
|
|
@@ -1620,11 +1758,27 @@ var TunnelOperations = class {
|
|
|
1620
1758
|
return {
|
|
1621
1759
|
tunnelid,
|
|
1622
1760
|
remoteurls,
|
|
1623
|
-
tunnelconfig: pinggyOptionsToTunnelConfig(tunnelConfig, configid, tunnelName, tlsInfo, greetMsg
|
|
1761
|
+
tunnelconfig: pinggyOptionsToTunnelConfig(tunnelConfig, configid, tunnelName, tlsInfo, greetMsg),
|
|
1624
1762
|
status: this.buildStatus(tunnelid, status, "" /* NoError */),
|
|
1625
1763
|
stats
|
|
1626
1764
|
};
|
|
1627
1765
|
}
|
|
1766
|
+
async buildTunnelResponseV2(tunnelid, tunnelConfig, configFromCli, configid, tunnelName, serve) {
|
|
1767
|
+
const [status, stats, greetMsg, remoteurls] = await Promise.all([
|
|
1768
|
+
this.tunnelManager.getTunnelStatus(tunnelid),
|
|
1769
|
+
this.tunnelManager.getLatestTunnelStats(tunnelid) || newStats(),
|
|
1770
|
+
this.tunnelManager.getTunnelGreetMessage(tunnelid),
|
|
1771
|
+
this.tunnelManager.getTunnelUrls(tunnelid)
|
|
1772
|
+
]);
|
|
1773
|
+
return {
|
|
1774
|
+
tunnelid,
|
|
1775
|
+
remoteurls,
|
|
1776
|
+
tunnelconfig: pinggyOptionsToTunnelConfigV1(tunnelConfig, configFromCli),
|
|
1777
|
+
status: this.buildStatus(tunnelid, status, "" /* NoError */),
|
|
1778
|
+
stats,
|
|
1779
|
+
greetmsg: greetMsg
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1628
1782
|
error(code, err, fallback) {
|
|
1629
1783
|
return newErrorResponse({
|
|
1630
1784
|
code,
|
|
@@ -1635,19 +1789,28 @@ var TunnelOperations = class {
|
|
|
1635
1789
|
async handleStart(config) {
|
|
1636
1790
|
try {
|
|
1637
1791
|
const opts = tunnelConfigToPinggyOptions(config);
|
|
1638
|
-
const
|
|
1639
|
-
const { tunnelid, instance, tunnelName, additionalForwarding, serve } = await this.tunnelManager.createTunnel({
|
|
1792
|
+
const { tunnelid, instance, tunnelName, serve, tunnelConfig } = await this.tunnelManager.createTunnel({
|
|
1640
1793
|
...opts,
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
serve: config.serve
|
|
1794
|
+
configId: config.configid,
|
|
1795
|
+
name: config.configname,
|
|
1796
|
+
optional: {
|
|
1797
|
+
serve: config.serve
|
|
1798
|
+
}
|
|
1647
1799
|
});
|
|
1648
|
-
this.tunnelManager.startTunnel(tunnelid);
|
|
1800
|
+
await this.tunnelManager.startTunnel(tunnelid);
|
|
1801
|
+
const tunnelPconfig = await this.tunnelManager.getTunnelConfig("", tunnelid);
|
|
1802
|
+
const resp = this.buildTunnelResponse(tunnelid, tunnelPconfig, config.configid, tunnelName, serve);
|
|
1803
|
+
return resp;
|
|
1804
|
+
} catch (err) {
|
|
1805
|
+
return this.error(ErrorCode.ErrorStartingTunnel, err, "Unknown error occurred while starting tunnel");
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
async handleStartV2(config) {
|
|
1809
|
+
try {
|
|
1810
|
+
const { tunnelid, instance, serve } = await this.tunnelManager.createTunnel(config);
|
|
1811
|
+
await this.tunnelManager.startTunnel(tunnelid);
|
|
1649
1812
|
const tunnelPconfig = await this.tunnelManager.getTunnelConfig("", tunnelid);
|
|
1650
|
-
const resp = this.
|
|
1813
|
+
const resp = this.buildTunnelResponseV2(tunnelid, tunnelPconfig, config, config.configId, config.name, config.serve);
|
|
1651
1814
|
return resp;
|
|
1652
1815
|
} catch (err) {
|
|
1653
1816
|
return this.error(ErrorCode.ErrorStartingTunnel, err, "Unknown error occurred while starting tunnel");
|
|
@@ -1658,20 +1821,59 @@ var TunnelOperations = class {
|
|
|
1658
1821
|
const opts = tunnelConfigToPinggyOptions(config);
|
|
1659
1822
|
const tunnel = await this.tunnelManager.updateConfig({
|
|
1660
1823
|
...opts,
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
serve: config.serve
|
|
1824
|
+
configId: config.configid,
|
|
1825
|
+
name: config.configname,
|
|
1826
|
+
optional: {
|
|
1827
|
+
serve: config.serve
|
|
1828
|
+
}
|
|
1667
1829
|
});
|
|
1668
1830
|
if (!tunnel.instance || !tunnel.tunnelConfig)
|
|
1669
1831
|
throw new Error("Invalid tunnel state after configuration update");
|
|
1670
|
-
return this.buildTunnelResponse(tunnel.tunnelid, tunnel.tunnelConfig, config.configid, tunnel.tunnelName, tunnel.
|
|
1832
|
+
return this.buildTunnelResponse(tunnel.tunnelid, tunnel.tunnelConfig, config.configid, tunnel.tunnelName, tunnel.serve);
|
|
1833
|
+
} catch (err) {
|
|
1834
|
+
return this.error(ErrorCode.InternalServerError, err, "Failed to update tunnel configuration");
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
async handleUpdateConfigV2(config) {
|
|
1838
|
+
try {
|
|
1839
|
+
const tunnel = await this.tunnelManager.updateConfig(config);
|
|
1840
|
+
if (!tunnel.instance || !tunnel.tunnelConfig)
|
|
1841
|
+
throw new Error("Invalid tunnel state after configuration update");
|
|
1842
|
+
return this.buildTunnelResponseV2(tunnel.tunnelid, tunnel.tunnelConfig, config, config.configId, tunnel.tunnelName, tunnel.serve);
|
|
1671
1843
|
} catch (err) {
|
|
1672
1844
|
return this.error(ErrorCode.InternalServerError, err, "Failed to update tunnel configuration");
|
|
1673
1845
|
}
|
|
1674
1846
|
}
|
|
1847
|
+
async handleListV2() {
|
|
1848
|
+
try {
|
|
1849
|
+
const tunnels = await this.tunnelManager.getAllTunnels();
|
|
1850
|
+
if (tunnels.length === 0) {
|
|
1851
|
+
return [];
|
|
1852
|
+
}
|
|
1853
|
+
return Promise.all(
|
|
1854
|
+
tunnels.map(async (t) => {
|
|
1855
|
+
const rawStats = this.tunnelManager.getLatestTunnelStats(t.tunnelid) || newStats();
|
|
1856
|
+
const [status, tlsInfo, greetMsg] = await Promise.all([
|
|
1857
|
+
this.tunnelManager.getTunnelStatus(t.tunnelid),
|
|
1858
|
+
this.tunnelManager.getLocalserverTlsInfo(t.tunnelid),
|
|
1859
|
+
this.tunnelManager.getTunnelGreetMessage(t.tunnelid)
|
|
1860
|
+
]);
|
|
1861
|
+
const tunnelConfguration = status !== "closed" /* Closed */ && status !== "exited" /* Exited */ ? await this.tunnelManager.getTunnelConfig("", t.tunnelid) : t.tunnelConfig;
|
|
1862
|
+
const tunnelConfig = pinggyOptionsToTunnelConfigV1(tunnelConfguration, t.tunnelConfig);
|
|
1863
|
+
return {
|
|
1864
|
+
tunnelid: t.tunnelid,
|
|
1865
|
+
remoteurls: t.remoteurls,
|
|
1866
|
+
status: this.buildStatus(t.tunnelid, status, "" /* NoError */),
|
|
1867
|
+
stats: rawStats,
|
|
1868
|
+
tunnelconfig: tunnelConfig,
|
|
1869
|
+
greetmsg: greetMsg
|
|
1870
|
+
};
|
|
1871
|
+
})
|
|
1872
|
+
);
|
|
1873
|
+
} catch (err) {
|
|
1874
|
+
return this.error(ErrorCode.InternalServerError, err, "Failed to list tunnels");
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1675
1877
|
async handleList() {
|
|
1676
1878
|
try {
|
|
1677
1879
|
const tunnels = await this.tunnelManager.getAllTunnels();
|
|
@@ -1687,7 +1889,7 @@ var TunnelOperations = class {
|
|
|
1687
1889
|
this.tunnelManager.getTunnelGreetMessage(t.tunnelid)
|
|
1688
1890
|
]);
|
|
1689
1891
|
const pinggyOptions = status !== "closed" /* Closed */ && status !== "exited" /* Exited */ ? await this.tunnelManager.getTunnelConfig("", t.tunnelid) : t.tunnelConfig;
|
|
1690
|
-
const tunnelConfig = pinggyOptionsToTunnelConfig(pinggyOptions, t.
|
|
1892
|
+
const tunnelConfig = pinggyOptionsToTunnelConfig(pinggyOptions, t.configId, t.tunnelName, tlsInfo, greetMsg, t.serve);
|
|
1691
1893
|
return {
|
|
1692
1894
|
tunnelid: t.tunnelid,
|
|
1693
1895
|
remoteurls: t.remoteurls,
|
|
@@ -1703,10 +1905,10 @@ var TunnelOperations = class {
|
|
|
1703
1905
|
}
|
|
1704
1906
|
async handleStop(tunnelid) {
|
|
1705
1907
|
try {
|
|
1706
|
-
const {
|
|
1908
|
+
const { configId } = this.tunnelManager.stopTunnel(tunnelid);
|
|
1707
1909
|
const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
|
|
1708
1910
|
if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
|
|
1709
|
-
return this.buildTunnelResponse(tunnelid, managed.tunnelConfig,
|
|
1911
|
+
return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, configId, managed.tunnelName, managed.serve);
|
|
1710
1912
|
} catch (err) {
|
|
1711
1913
|
return this.error(ErrorCode.TunnelNotFound, err, "Failed to stop tunnel");
|
|
1712
1914
|
}
|
|
@@ -1715,7 +1917,7 @@ var TunnelOperations = class {
|
|
|
1715
1917
|
try {
|
|
1716
1918
|
const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
|
|
1717
1919
|
if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
|
|
1718
|
-
return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, managed.
|
|
1920
|
+
return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, managed.configId, managed.tunnelName, managed.serve);
|
|
1719
1921
|
} catch (err) {
|
|
1720
1922
|
return this.error(ErrorCode.TunnelNotFound, err, "Failed to get tunnel information");
|
|
1721
1923
|
}
|
|
@@ -1725,7 +1927,7 @@ var TunnelOperations = class {
|
|
|
1725
1927
|
await this.tunnelManager.restartTunnel(tunnelid);
|
|
1726
1928
|
const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
|
|
1727
1929
|
if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
|
|
1728
|
-
return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, managed.
|
|
1930
|
+
return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, managed.configId, managed.tunnelName, managed.serve);
|
|
1729
1931
|
} catch (err) {
|
|
1730
1932
|
return this.error(ErrorCode.TunnelNotFound, err, "Failed to restart tunnel");
|
|
1731
1933
|
}
|
|
@@ -1769,11 +1971,198 @@ var TunnelOperations = class {
|
|
|
1769
1971
|
// src/remote_management/remoteManagement.ts
|
|
1770
1972
|
import WebSocket from "ws";
|
|
1771
1973
|
|
|
1974
|
+
// src/remote_management/websocket_printer.ts
|
|
1975
|
+
import pico3 from "picocolors";
|
|
1976
|
+
var PENDING_START_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
1977
|
+
var RemoteManagementWebSocketPrinter = class {
|
|
1978
|
+
constructor() {
|
|
1979
|
+
this.tunnelManager = TunnelManager.getInstance();
|
|
1980
|
+
this.pendingStarts = /* @__PURE__ */ new Map();
|
|
1981
|
+
}
|
|
1982
|
+
setTunnelHandler(tunnelHandler) {
|
|
1983
|
+
this.tunnelHandler = tunnelHandler;
|
|
1984
|
+
}
|
|
1985
|
+
queueStart(config) {
|
|
1986
|
+
this.cleanupExpiredPendingStarts();
|
|
1987
|
+
const entry = {
|
|
1988
|
+
configId: this.getConfigIdFromRequest(config),
|
|
1989
|
+
configName: this.getConfigNameFromRequest(config),
|
|
1990
|
+
queuedAt: Date.now()
|
|
1991
|
+
};
|
|
1992
|
+
this.latestPendingConfigId = entry.configId;
|
|
1993
|
+
this.pendingStarts.set(entry.configId, entry);
|
|
1994
|
+
printer_default.startSpinner("Starting tunnel with config name: " + entry.configName);
|
|
1995
|
+
}
|
|
1996
|
+
failQueuedStart(config, reason) {
|
|
1997
|
+
const configId = this.getConfigIdFromRequest(config);
|
|
1998
|
+
const pending = this.pendingStarts.get(configId);
|
|
1999
|
+
const configName = pending?.configName || this.getConfigNameFromRequest(config);
|
|
2000
|
+
this.pendingStarts.delete(configId);
|
|
2001
|
+
if (this.latestPendingConfigId === configId) {
|
|
2002
|
+
this.latestPendingConfigId = void 0;
|
|
2003
|
+
printer_default.stopSpinnerFail(`Failed to start tunnel with config name: ${configName}. ${reason}`);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
handleStartResult(config, result) {
|
|
2007
|
+
this.cleanupExpiredPendingStarts();
|
|
2008
|
+
const requestedConfigId = this.getConfigIdFromRequest(config);
|
|
2009
|
+
if (this.latestPendingConfigId && requestedConfigId !== this.latestPendingConfigId) {
|
|
2010
|
+
this.pendingStarts.delete(requestedConfigId);
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
if (isErrorResponse(result)) {
|
|
2014
|
+
this.failQueuedStart(config, result.message);
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
const configId = this.getConfigIdFromTunnel(result);
|
|
2018
|
+
const pending = this.pendingStarts.get(requestedConfigId) || {
|
|
2019
|
+
configId: requestedConfigId,
|
|
2020
|
+
configName: this.getConfigNameFromRequest(config),
|
|
2021
|
+
queuedAt: Date.now()
|
|
2022
|
+
};
|
|
2023
|
+
pending.tunnelId = result.tunnelid;
|
|
2024
|
+
this.pendingStarts.set(requestedConfigId, pending);
|
|
2025
|
+
if (result.remoteurls.length > 0) {
|
|
2026
|
+
this.completePendingStart(pending, result.remoteurls);
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
printStopRequested(tunnelId) {
|
|
2030
|
+
const details = this.resolveTunnelDetails(tunnelId);
|
|
2031
|
+
printer_default.startSpinner("Stopping tunnel with config name: " + details.configName);
|
|
2032
|
+
}
|
|
2033
|
+
handleStopResult(tunnelId, result) {
|
|
2034
|
+
const details = this.resolveTunnelDetails(tunnelId, result);
|
|
2035
|
+
if (isErrorResponse(result)) {
|
|
2036
|
+
printer_default.stopSpinnerFail("Failed to stop tunnel with config name: " + details.configName);
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
2039
|
+
this.pendingStarts.delete(details.configId);
|
|
2040
|
+
printer_default.stopSpinnerSuccess("Stopped tunnel with config name: " + details.configName);
|
|
2041
|
+
}
|
|
2042
|
+
printRestartRequested(tunnelId) {
|
|
2043
|
+
const details = this.resolveTunnelDetails(tunnelId);
|
|
2044
|
+
printer_default.startSpinner("Restarting tunnel with config name: " + details.configName);
|
|
2045
|
+
}
|
|
2046
|
+
handleRestartResult(tunnelId, result) {
|
|
2047
|
+
const details = this.resolveTunnelDetails(tunnelId, result);
|
|
2048
|
+
if (isErrorResponse(result)) {
|
|
2049
|
+
printer_default.warn(`Failed to restart tunnel with config name: ${details.configName}. ${result.message}`);
|
|
2050
|
+
printer_default.stopSpinnerFail("Failed to restart tunnel with config name: " + details.configName);
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
printer_default.stopSpinnerSuccess("Restarted tunnel with config name: " + details.configName);
|
|
2054
|
+
if (result.remoteurls?.length > 0) {
|
|
2055
|
+
printer_default.info(pico3.cyanBright("Remote URLs:"));
|
|
2056
|
+
(result.remoteurls ?? []).forEach(
|
|
2057
|
+
(url) => printer_default.print(" " + pico3.magentaBright(url))
|
|
2058
|
+
);
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
monitorList(result) {
|
|
2062
|
+
this.cleanupExpiredPendingStarts();
|
|
2063
|
+
if (!Array.isArray(result) || this.pendingStarts.size === 0 || !this.latestPendingConfigId) {
|
|
2064
|
+
return;
|
|
2065
|
+
}
|
|
2066
|
+
for (const tunnel of result) {
|
|
2067
|
+
const pending = this.findPendingStart(tunnel);
|
|
2068
|
+
if (!pending) {
|
|
2069
|
+
continue;
|
|
2070
|
+
}
|
|
2071
|
+
if (pending.configId !== this.latestPendingConfigId) {
|
|
2072
|
+
continue;
|
|
2073
|
+
}
|
|
2074
|
+
pending.tunnelId = tunnel.tunnelid;
|
|
2075
|
+
this.pendingStarts.set(pending.configId, pending);
|
|
2076
|
+
if (tunnel.remoteurls.length > 0) {
|
|
2077
|
+
this.completePendingStart(pending, tunnel.remoteurls);
|
|
2078
|
+
continue;
|
|
2079
|
+
}
|
|
2080
|
+
if (tunnel.status.state === "exited" /* Exited */) {
|
|
2081
|
+
const reason = tunnel.status.errormsg || "Tunnel exited before a public URL was assigned";
|
|
2082
|
+
this.pendingStarts.delete(pending.configId);
|
|
2083
|
+
this.latestPendingConfigId = void 0;
|
|
2084
|
+
printer_default.stopSpinnerFail(`Tunnel start did not complete for config name: ${pending.configName}. ${reason}`);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
completePendingStart(entry, urls) {
|
|
2089
|
+
if (this.latestPendingConfigId && entry.configId !== this.latestPendingConfigId) {
|
|
2090
|
+
this.pendingStarts.delete(entry.configId);
|
|
2091
|
+
return;
|
|
2092
|
+
}
|
|
2093
|
+
this.pendingStarts.delete(entry.configId);
|
|
2094
|
+
this.latestPendingConfigId = void 0;
|
|
2095
|
+
printer_default.stopSpinnerSuccess(`Tunnel started with config name: ${entry.configName}.`);
|
|
2096
|
+
printer_default.info(pico3.cyanBright("Remote URLs:"));
|
|
2097
|
+
(urls ?? []).forEach(
|
|
2098
|
+
(url) => printer_default.print(" " + pico3.magentaBright(url))
|
|
2099
|
+
);
|
|
2100
|
+
}
|
|
2101
|
+
cleanupExpiredPendingStarts() {
|
|
2102
|
+
const now = Date.now();
|
|
2103
|
+
for (const [configId, entry] of this.pendingStarts.entries()) {
|
|
2104
|
+
if (now - entry.queuedAt <= PENDING_START_TIMEOUT_MS) {
|
|
2105
|
+
continue;
|
|
2106
|
+
}
|
|
2107
|
+
this.pendingStarts.delete(configId);
|
|
2108
|
+
printer_default.warn(`Timed out while waiting for tunnel URL for config name: ${entry.configName}`);
|
|
2109
|
+
logger.warn("Pending websocket start entry expired", { configId, tunnelId: entry.tunnelId });
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
findPendingStart(tunnel) {
|
|
2113
|
+
const configId = this.getConfigIdFromTunnel(tunnel);
|
|
2114
|
+
const byConfigId = this.pendingStarts.get(configId);
|
|
2115
|
+
if (byConfigId) {
|
|
2116
|
+
return byConfigId;
|
|
2117
|
+
}
|
|
2118
|
+
for (const entry of this.pendingStarts.values()) {
|
|
2119
|
+
if (entry.tunnelId === tunnel.tunnelid) {
|
|
2120
|
+
return entry;
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
return void 0;
|
|
2124
|
+
}
|
|
2125
|
+
resolveTunnelDetails(tunnelId, result) {
|
|
2126
|
+
try {
|
|
2127
|
+
const managed = this.tunnelManager.getManagedTunnel(void 0, tunnelId);
|
|
2128
|
+
return {
|
|
2129
|
+
configId: managed.configId,
|
|
2130
|
+
configName: managed.tunnelName || managed.configId || tunnelId
|
|
2131
|
+
};
|
|
2132
|
+
} catch {
|
|
2133
|
+
if (result && !isErrorResponse(result)) {
|
|
2134
|
+
return {
|
|
2135
|
+
configId: this.getConfigIdFromTunnel(result),
|
|
2136
|
+
configName: this.getConfigNameFromTunnel(result)
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
return {
|
|
2140
|
+
configId: tunnelId,
|
|
2141
|
+
configName: tunnelId
|
|
2142
|
+
};
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
getConfigIdFromRequest(config) {
|
|
2146
|
+
return "configid" in config ? config.configid : config.configId;
|
|
2147
|
+
}
|
|
2148
|
+
getConfigNameFromRequest(config) {
|
|
2149
|
+
return "configname" in config ? config.configname : config.name;
|
|
2150
|
+
}
|
|
2151
|
+
getConfigIdFromTunnel(tunnel) {
|
|
2152
|
+
return "configid" in tunnel.tunnelconfig ? tunnel.tunnelconfig.configid : tunnel.tunnelconfig.configId;
|
|
2153
|
+
}
|
|
2154
|
+
getConfigNameFromTunnel(tunnel) {
|
|
2155
|
+
return "configname" in tunnel.tunnelconfig ? tunnel.tunnelconfig.configname : tunnel.tunnelconfig.name;
|
|
2156
|
+
}
|
|
2157
|
+
};
|
|
2158
|
+
var remoteManagementWebSocketPrinter = new RemoteManagementWebSocketPrinter();
|
|
2159
|
+
|
|
1772
2160
|
// src/remote_management/websocket_handlers.ts
|
|
1773
2161
|
import z2 from "zod";
|
|
1774
2162
|
var WebSocketCommandHandler = class {
|
|
1775
2163
|
constructor() {
|
|
1776
2164
|
this.tunnelHandler = new TunnelOperations();
|
|
2165
|
+
remoteManagementWebSocketPrinter.setTunnelHandler(this.tunnelHandler);
|
|
1777
2166
|
}
|
|
1778
2167
|
safeParse(text) {
|
|
1779
2168
|
if (!text) return void 0;
|
|
@@ -1798,35 +2187,157 @@ var WebSocketCommandHandler = class {
|
|
|
1798
2187
|
this.sendResponse(ws, resp);
|
|
1799
2188
|
}
|
|
1800
2189
|
async handleStartReq(req, raw) {
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
2190
|
+
let queuedConfig;
|
|
2191
|
+
try {
|
|
2192
|
+
const dc = StartSchema.parse(raw);
|
|
2193
|
+
queuedConfig = dc.tunnelConfig;
|
|
2194
|
+
remoteManagementWebSocketPrinter.queueStart(dc.tunnelConfig);
|
|
2195
|
+
const result = await this.tunnelHandler.handleStart(dc.tunnelConfig);
|
|
2196
|
+
remoteManagementWebSocketPrinter.handleStartResult(dc.tunnelConfig, result);
|
|
2197
|
+
return this.wrapResponse(result, req);
|
|
2198
|
+
} catch (e) {
|
|
2199
|
+
if (queuedConfig) {
|
|
2200
|
+
remoteManagementWebSocketPrinter.failQueuedStart(queuedConfig, String(e));
|
|
2201
|
+
}
|
|
2202
|
+
if (e instanceof z2.ZodError) {
|
|
2203
|
+
printer_default.warn("Validation failed for start request");
|
|
2204
|
+
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
2205
|
+
}
|
|
2206
|
+
printer_default.warn(`Error in handleStartReq error: ${String(e)}`);
|
|
2207
|
+
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
async handleStartV2Req(req, raw) {
|
|
2211
|
+
let queuedConfig;
|
|
2212
|
+
try {
|
|
2213
|
+
const dc = StartV2Schema.parse(raw);
|
|
2214
|
+
queuedConfig = dc.tunnelConfig;
|
|
2215
|
+
remoteManagementWebSocketPrinter.queueStart(dc.tunnelConfig);
|
|
2216
|
+
const result = await this.tunnelHandler.handleStartV2(dc.tunnelConfig);
|
|
2217
|
+
remoteManagementWebSocketPrinter.handleStartResult(dc.tunnelConfig, result);
|
|
2218
|
+
return this.wrapResponse(result, req);
|
|
2219
|
+
} catch (e) {
|
|
2220
|
+
if (queuedConfig) {
|
|
2221
|
+
remoteManagementWebSocketPrinter.failQueuedStart(queuedConfig, String(e));
|
|
2222
|
+
}
|
|
2223
|
+
if (e instanceof z2.ZodError) {
|
|
2224
|
+
printer_default.warn("Validation failed for start-v2 request");
|
|
2225
|
+
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
2226
|
+
}
|
|
2227
|
+
printer_default.warn(`Error in handleStartV2Req error: ${String(e)}`);
|
|
2228
|
+
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
2229
|
+
}
|
|
1805
2230
|
}
|
|
1806
2231
|
async handleStopReq(req, raw) {
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
2232
|
+
try {
|
|
2233
|
+
const dc = StopSchema.parse(raw);
|
|
2234
|
+
remoteManagementWebSocketPrinter.printStopRequested(dc.tunnelID);
|
|
2235
|
+
const result = await this.tunnelHandler.handleStop(dc.tunnelID);
|
|
2236
|
+
remoteManagementWebSocketPrinter.handleStopResult(dc.tunnelID, result);
|
|
2237
|
+
return this.wrapResponse(result, req);
|
|
2238
|
+
} catch (e) {
|
|
2239
|
+
if (e instanceof z2.ZodError) {
|
|
2240
|
+
printer_default.warn("Validation failed for stop request");
|
|
2241
|
+
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
2242
|
+
}
|
|
2243
|
+
printer_default.warn(`Error in handleStopReq error: ${String(e)}`);
|
|
2244
|
+
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
2245
|
+
}
|
|
1811
2246
|
}
|
|
1812
2247
|
async handleGetReq(req, raw) {
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
2248
|
+
try {
|
|
2249
|
+
const dc = GetSchema.parse(raw);
|
|
2250
|
+
const result = await this.tunnelHandler.handleGet(dc.tunnelID);
|
|
2251
|
+
return this.wrapResponse(result, req);
|
|
2252
|
+
} catch (e) {
|
|
2253
|
+
if (e instanceof z2.ZodError) {
|
|
2254
|
+
printer_default.warn("Validation failed for get request");
|
|
2255
|
+
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
2256
|
+
}
|
|
2257
|
+
printer_default.warn(`Error in handleGetReq error: ${String(e)}`);
|
|
2258
|
+
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
2259
|
+
}
|
|
1816
2260
|
}
|
|
1817
2261
|
async handleRestartReq(req, raw) {
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
2262
|
+
try {
|
|
2263
|
+
const dc = RestartSchema.parse(raw);
|
|
2264
|
+
remoteManagementWebSocketPrinter.printRestartRequested(dc.tunnelID);
|
|
2265
|
+
const result = await this.tunnelHandler.handleRestart(dc.tunnelID);
|
|
2266
|
+
remoteManagementWebSocketPrinter.handleRestartResult(dc.tunnelID, result);
|
|
2267
|
+
return this.wrapResponse(result, req);
|
|
2268
|
+
} catch (e) {
|
|
2269
|
+
if (e instanceof z2.ZodError) {
|
|
2270
|
+
printer_default.warn("Validation failed for restart request");
|
|
2271
|
+
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
2272
|
+
}
|
|
2273
|
+
printer_default.warn(`Error in handleRestartReq error: ${String(e)}`);
|
|
2274
|
+
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
2275
|
+
}
|
|
1821
2276
|
}
|
|
1822
2277
|
async handleUpdateConfigReq(req, raw) {
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
2278
|
+
try {
|
|
2279
|
+
const dc = UpdateConfigSchema.parse(raw);
|
|
2280
|
+
const result = await this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig);
|
|
2281
|
+
return this.wrapResponse(result, req);
|
|
2282
|
+
} catch (e) {
|
|
2283
|
+
if (e instanceof z2.ZodError) {
|
|
2284
|
+
printer_default.warn("Validation failed for updateconfig request");
|
|
2285
|
+
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
2286
|
+
}
|
|
2287
|
+
printer_default.warn(`Error in handleUpdateConfigReq error: ${String(e)}`);
|
|
2288
|
+
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
async handleUpdateConfigV2Req(req, raw) {
|
|
2292
|
+
try {
|
|
2293
|
+
const dc = UpdateConfigV2Schema.parse(raw);
|
|
2294
|
+
const result = await this.tunnelHandler.handleUpdateConfigV2(dc.tunnelConfig);
|
|
2295
|
+
return this.wrapResponse(result, req);
|
|
2296
|
+
} catch (e) {
|
|
2297
|
+
if (e instanceof z2.ZodError) {
|
|
2298
|
+
printer_default.warn("Validation failed for update-config-v2 request");
|
|
2299
|
+
return NewErrorResponseObject({ code: ErrorCode.InvalidBodyFormatError, message: "Validation failed" });
|
|
2300
|
+
}
|
|
2301
|
+
printer_default.warn(`Error in handleUpdateConfigV2Req error: ${String(e)}`);
|
|
2302
|
+
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
2303
|
+
}
|
|
1826
2304
|
}
|
|
1827
2305
|
async handleListReq(req) {
|
|
1828
|
-
|
|
1829
|
-
|
|
2306
|
+
try {
|
|
2307
|
+
const result = await this.tunnelHandler.handleList();
|
|
2308
|
+
remoteManagementWebSocketPrinter.monitorList(result);
|
|
2309
|
+
return this.wrapResponse(result, req);
|
|
2310
|
+
} catch (e) {
|
|
2311
|
+
printer_default.warn(`Error in handleListReq error: ${String(e)}`);
|
|
2312
|
+
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
async handleListV2Req(req) {
|
|
2316
|
+
try {
|
|
2317
|
+
const result = await this.tunnelHandler.handleListV2();
|
|
2318
|
+
remoteManagementWebSocketPrinter.monitorList(result);
|
|
2319
|
+
return this.wrapResponse(result, req);
|
|
2320
|
+
} catch (e) {
|
|
2321
|
+
printer_default.warn(`Error in handleListV2Req error: ${String(e)}`);
|
|
2322
|
+
return NewErrorResponseObject({ code: ErrorCode.InternalServerError, message: String(e) });
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
async handleGetVersionReq(ws, req) {
|
|
2326
|
+
try {
|
|
2327
|
+
const versionResponse = {
|
|
2328
|
+
cli_version: getVersion()
|
|
2329
|
+
};
|
|
2330
|
+
const payload = {
|
|
2331
|
+
command: req.command,
|
|
2332
|
+
requestid: req.requestid,
|
|
2333
|
+
response: JSON.stringify(versionResponse),
|
|
2334
|
+
error: false
|
|
2335
|
+
};
|
|
2336
|
+
ws.send(JSON.stringify(payload));
|
|
2337
|
+
} catch (e) {
|
|
2338
|
+
printer_default.warn(`Error in handleGetVersionReq error: ${String(e)}`);
|
|
2339
|
+
this.sendError(ws, req, String(e));
|
|
2340
|
+
}
|
|
1830
2341
|
}
|
|
1831
2342
|
wrapResponse(result, req) {
|
|
1832
2343
|
if (isErrorResponse(result)) {
|
|
@@ -1860,6 +2371,10 @@ var WebSocketCommandHandler = class {
|
|
|
1860
2371
|
response = await this.handleStartReq(req, raw);
|
|
1861
2372
|
break;
|
|
1862
2373
|
}
|
|
2374
|
+
case "start-v2": {
|
|
2375
|
+
response = await this.handleStartV2Req(req, raw);
|
|
2376
|
+
break;
|
|
2377
|
+
}
|
|
1863
2378
|
case "stop": {
|
|
1864
2379
|
response = await this.handleStopReq(req, raw);
|
|
1865
2380
|
break;
|
|
@@ -1876,10 +2391,22 @@ var WebSocketCommandHandler = class {
|
|
|
1876
2391
|
response = await this.handleUpdateConfigReq(req, raw);
|
|
1877
2392
|
break;
|
|
1878
2393
|
}
|
|
2394
|
+
case "update-config-v2": {
|
|
2395
|
+
response = await this.handleUpdateConfigV2Req(req, raw);
|
|
2396
|
+
break;
|
|
2397
|
+
}
|
|
1879
2398
|
case "list": {
|
|
1880
2399
|
response = await this.handleListReq(req);
|
|
1881
2400
|
break;
|
|
1882
2401
|
}
|
|
2402
|
+
case "list-v2": {
|
|
2403
|
+
response = await this.handleListV2Req(req);
|
|
2404
|
+
break;
|
|
2405
|
+
}
|
|
2406
|
+
case "get-version": {
|
|
2407
|
+
await this.handleGetVersionReq(ws, req);
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
1883
2410
|
default:
|
|
1884
2411
|
if (typeof req.command === "string") {
|
|
1885
2412
|
logger.warn("Unknown command", { command: req.command });
|
|
@@ -1898,6 +2425,18 @@ var WebSocketCommandHandler = class {
|
|
|
1898
2425
|
}
|
|
1899
2426
|
}
|
|
1900
2427
|
};
|
|
2428
|
+
function sendVersionResponse(ws) {
|
|
2429
|
+
const versionResponse = {
|
|
2430
|
+
cli_version: getVersion()
|
|
2431
|
+
};
|
|
2432
|
+
const payload = {
|
|
2433
|
+
command: "get-version",
|
|
2434
|
+
requestid: "0",
|
|
2435
|
+
response: JSON.stringify(versionResponse),
|
|
2436
|
+
error: false
|
|
2437
|
+
};
|
|
2438
|
+
ws.send(JSON.stringify(payload));
|
|
2439
|
+
}
|
|
1901
2440
|
function handleConnectionStatusMessage(firstMessage) {
|
|
1902
2441
|
try {
|
|
1903
2442
|
const text = typeof firstMessage === "string" ? firstMessage : firstMessage.toString();
|
|
@@ -1948,7 +2487,11 @@ async function parseRemoteManagement(values) {
|
|
|
1948
2487
|
if (typeof rmToken === "string" && rmToken.trim().length > 0) {
|
|
1949
2488
|
const manageHost = values["manage"];
|
|
1950
2489
|
try {
|
|
1951
|
-
|
|
2490
|
+
const remoteManagementConfig = {
|
|
2491
|
+
apiKey: rmToken,
|
|
2492
|
+
serverUrl: buildRemoteManagementWsUrl(manageHost)
|
|
2493
|
+
};
|
|
2494
|
+
await initiateRemoteManagement(remoteManagementConfig);
|
|
1952
2495
|
return { ok: true };
|
|
1953
2496
|
} catch (e) {
|
|
1954
2497
|
logger.error("Failed to initiate remote management:", e);
|
|
@@ -1956,11 +2499,11 @@ async function parseRemoteManagement(values) {
|
|
|
1956
2499
|
}
|
|
1957
2500
|
}
|
|
1958
2501
|
}
|
|
1959
|
-
async function initiateRemoteManagement(
|
|
1960
|
-
if (!
|
|
2502
|
+
async function initiateRemoteManagement(remoteManagementConfig) {
|
|
2503
|
+
if (!remoteManagementConfig.apiKey || remoteManagementConfig.apiKey.trim().length === 0) {
|
|
1961
2504
|
throw new Error("Remote management token is required (use --remote-management <TOKEN>)");
|
|
1962
2505
|
}
|
|
1963
|
-
const wsUrl =
|
|
2506
|
+
const wsUrl = remoteManagementConfig.serverUrl;
|
|
1964
2507
|
const wsHost = extractHostname(wsUrl);
|
|
1965
2508
|
logger.info("Remote management mode enabled.");
|
|
1966
2509
|
_stopRequested = false;
|
|
@@ -1976,7 +2519,7 @@ async function initiateRemoteManagement(token, manage) {
|
|
|
1976
2519
|
logConnecting();
|
|
1977
2520
|
setRemoteManagementState({ status: RemoteManagementStatus.Connecting, errorMessage: "" });
|
|
1978
2521
|
try {
|
|
1979
|
-
await handleWebSocketConnection(wsUrl, wsHost,
|
|
2522
|
+
await handleWebSocketConnection(wsUrl, wsHost, remoteManagementConfig.apiKey);
|
|
1980
2523
|
} catch (error) {
|
|
1981
2524
|
logger.warn("Remote management connection error", { error: String(error) });
|
|
1982
2525
|
}
|
|
@@ -2004,6 +2547,7 @@ async function handleWebSocketConnection(wsUrl, wsHost, token) {
|
|
|
2004
2547
|
};
|
|
2005
2548
|
ws.once("open", () => {
|
|
2006
2549
|
printer_default.success(`Connected to ${wsHost}`);
|
|
2550
|
+
setRemoteManagementState({ status: RemoteManagementStatus.Running, errorMessage: "" });
|
|
2007
2551
|
heartbeat = setInterval(() => {
|
|
2008
2552
|
if (ws.readyState === WebSocket.OPEN) ws.ping();
|
|
2009
2553
|
}, PING_INTERVAL_MS);
|
|
@@ -2014,7 +2558,11 @@ async function handleWebSocketConnection(wsUrl, wsHost, token) {
|
|
|
2014
2558
|
if (firstMessage) {
|
|
2015
2559
|
firstMessage = false;
|
|
2016
2560
|
const ok = handleConnectionStatusMessage(data);
|
|
2017
|
-
if (!ok)
|
|
2561
|
+
if (!ok) {
|
|
2562
|
+
ws.close();
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
sendVersionResponse(ws);
|
|
2018
2566
|
return;
|
|
2019
2567
|
}
|
|
2020
2568
|
setRemoteManagementState({ status: RemoteManagementStatus.Running, errorMessage: "" });
|
|
@@ -2025,20 +2573,21 @@ async function handleWebSocketConnection(wsUrl, wsHost, token) {
|
|
|
2025
2573
|
}
|
|
2026
2574
|
});
|
|
2027
2575
|
ws.on("unexpected-response", (_, res) => {
|
|
2028
|
-
setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: `HTTP ${res.statusCode}` });
|
|
2029
2576
|
if (res.statusCode === 401) {
|
|
2577
|
+
setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: `HTTP ${res.statusCode}` });
|
|
2030
2578
|
printer_default.error("Unauthorized. Please enter a valid token.");
|
|
2031
2579
|
logger.error("Unauthorized (401) on remote management connect");
|
|
2580
|
+
ws.close();
|
|
2032
2581
|
} else {
|
|
2582
|
+
logger.warn("Unexpected HTTP response ", { statusCode: res.statusCode });
|
|
2033
2583
|
printer_default.warn(`Unexpected HTTP ${res.statusCode}. Retrying...`);
|
|
2034
|
-
|
|
2584
|
+
cleanup();
|
|
2035
2585
|
}
|
|
2036
|
-
ws.close();
|
|
2037
2586
|
});
|
|
2038
2587
|
ws.on("close", (code, reason) => {
|
|
2039
2588
|
setRemoteManagementState({ status: RemoteManagementStatus.NotRunning, errorMessage: "" });
|
|
2040
2589
|
logger.info("WebSocket closed", { code, reason: reason.toString() });
|
|
2041
|
-
printer_default.warn(`Disconnected (code: ${code}). Retrying...`);
|
|
2590
|
+
printer_default.warn(`Disconnected (code: ${code}). Retrying in ${RECONNECT_SLEEP_MS / 1e3}s...`);
|
|
2042
2591
|
cleanup();
|
|
2043
2592
|
});
|
|
2044
2593
|
ws.on("error", (err) => {
|