pinggy 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-T5ESYDJY.js +121 -0
- package/dist/index.cjs +2267 -1885
- package/dist/index.d.cts +0 -551
- package/dist/index.d.ts +0 -551
- package/dist/index.js +102 -3815
- package/dist/main-CZY6GID4.js +3726 -0
- package/package.json +1 -1
- package/src/cli/buildConfig.ts +83 -91
- package/src/index.ts +21 -78
- package/src/main.ts +87 -0
- package/src/utils/detect_vc_redist_on_windows.ts +167 -0
package/dist/index.cjs
CHANGED
|
@@ -6,6 +6,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
9
12
|
var __export = (target, all) => {
|
|
10
13
|
for (var name in all)
|
|
11
14
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -26,40 +29,155 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
29
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
30
|
mod
|
|
28
31
|
));
|
|
29
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
32
|
|
|
31
|
-
//
|
|
32
|
-
var
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
initiateRemoteManagement: () => initiateRemoteManagement
|
|
33
|
+
// node_modules/tsup/assets/cjs_shims.js
|
|
34
|
+
var getImportMetaUrl, importMetaUrl;
|
|
35
|
+
var init_cjs_shims = __esm({
|
|
36
|
+
"node_modules/tsup/assets/cjs_shims.js"() {
|
|
37
|
+
"use strict";
|
|
38
|
+
getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
39
|
+
importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
40
|
+
}
|
|
40
41
|
});
|
|
41
|
-
module.exports = __toCommonJS(index_exports);
|
|
42
42
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
// src/tui/spinner/spinner.ts
|
|
44
|
+
function startSpinner(name = "dots", text = "Loading") {
|
|
45
|
+
const spinner = spinners[name];
|
|
46
|
+
let i = 0;
|
|
47
|
+
currentText = text;
|
|
48
|
+
if (currentTimer) {
|
|
49
|
+
clearInterval(currentTimer);
|
|
50
|
+
}
|
|
51
|
+
currentTimer = setInterval(() => {
|
|
52
|
+
const frame = spinner.frames[i = ++i % spinner.frames.length];
|
|
53
|
+
process.stdout.write(`\r${import_picocolors.default.cyan(frame)} ${text}`);
|
|
54
|
+
}, spinner.interval);
|
|
55
|
+
return () => stopSpinner();
|
|
56
|
+
}
|
|
57
|
+
function stopSpinner() {
|
|
58
|
+
if (currentTimer) {
|
|
59
|
+
clearInterval(currentTimer);
|
|
60
|
+
currentTimer = null;
|
|
61
|
+
process.stdout.write("\r\x1B[K");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function stopSpinnerSuccess(message) {
|
|
65
|
+
if (currentTimer) {
|
|
66
|
+
clearInterval(currentTimer);
|
|
67
|
+
currentTimer = null;
|
|
68
|
+
const finalMessage = message || currentText;
|
|
69
|
+
process.stdout.write(`\r${import_picocolors.default.green("\u2714")} ${finalMessage}
|
|
70
|
+
`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function stopSpinnerFail(message) {
|
|
74
|
+
if (currentTimer) {
|
|
75
|
+
clearInterval(currentTimer);
|
|
76
|
+
currentTimer = null;
|
|
77
|
+
const finalMessage = message || currentText;
|
|
78
|
+
process.stdout.write(`\r${import_picocolors.default.red("\u2716")} ${finalMessage}
|
|
79
|
+
`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
var import_picocolors, spinners, currentTimer, currentText;
|
|
83
|
+
var init_spinner = __esm({
|
|
84
|
+
"src/tui/spinner/spinner.ts"() {
|
|
85
|
+
"use strict";
|
|
86
|
+
init_cjs_shims();
|
|
87
|
+
import_picocolors = __toESM(require("picocolors"), 1);
|
|
88
|
+
spinners = {
|
|
89
|
+
dots: {
|
|
90
|
+
interval: 80,
|
|
91
|
+
frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"]
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
currentTimer = null;
|
|
95
|
+
currentText = "";
|
|
96
|
+
}
|
|
97
|
+
});
|
|
46
98
|
|
|
47
|
-
// src/
|
|
48
|
-
var
|
|
99
|
+
// src/utils/printer.ts
|
|
100
|
+
var import_picocolors2, _CLIPrinter, CLIPrinter, printer_default;
|
|
101
|
+
var init_printer = __esm({
|
|
102
|
+
"src/utils/printer.ts"() {
|
|
103
|
+
"use strict";
|
|
104
|
+
init_cjs_shims();
|
|
105
|
+
import_picocolors2 = __toESM(require("picocolors"), 1);
|
|
106
|
+
init_spinner();
|
|
107
|
+
_CLIPrinter = class _CLIPrinter {
|
|
108
|
+
static isCLIError(err) {
|
|
109
|
+
return err instanceof Error;
|
|
110
|
+
}
|
|
111
|
+
static print(message, ...args) {
|
|
112
|
+
console.log(message, ...args);
|
|
113
|
+
}
|
|
114
|
+
static error(err) {
|
|
115
|
+
const def = this.errorDefinitions.find((d) => d.match(err));
|
|
116
|
+
const msg = def.message(err);
|
|
117
|
+
console.error(import_picocolors2.default.red(import_picocolors2.default.bold("\u2716 Error:")), import_picocolors2.default.red(msg));
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
static warn(message) {
|
|
121
|
+
console.warn(import_picocolors2.default.yellow(import_picocolors2.default.bold("\u26A0 Warning:")), import_picocolors2.default.yellow(message));
|
|
122
|
+
}
|
|
123
|
+
static warnTxt(message) {
|
|
124
|
+
console.warn(import_picocolors2.default.yellow(import_picocolors2.default.bold("\u26A0 Warning:")), import_picocolors2.default.yellow(message));
|
|
125
|
+
}
|
|
126
|
+
static success(message) {
|
|
127
|
+
console.log(import_picocolors2.default.green(import_picocolors2.default.bold(" \u2714 Success:")), import_picocolors2.default.green(message));
|
|
128
|
+
}
|
|
129
|
+
static async info(message) {
|
|
130
|
+
console.log(import_picocolors2.default.blue(message));
|
|
131
|
+
}
|
|
132
|
+
static startSpinner(message) {
|
|
133
|
+
startSpinner("dots", message);
|
|
134
|
+
}
|
|
135
|
+
static stopSpinnerSuccess(message) {
|
|
136
|
+
stopSpinnerSuccess(message);
|
|
137
|
+
}
|
|
138
|
+
static stopSpinnerFail(message) {
|
|
139
|
+
stopSpinnerFail(message);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
_CLIPrinter.errorDefinitions = [
|
|
143
|
+
{
|
|
144
|
+
match: (err) => _CLIPrinter.isCLIError(err) && err.code === "ERR_PARSE_ARGS_UNKNOWN_OPTION",
|
|
145
|
+
message: (err) => {
|
|
146
|
+
const match = /Unknown option '(.+?)'/.exec(err.message);
|
|
147
|
+
const option = match ? match[1] : "(unknown)";
|
|
148
|
+
return `Unknown option '${option}'. Please check your command or use pinggy --h for guidance.`;
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
match: (err) => _CLIPrinter.isCLIError(err) && err.code === "ERR_PARSE_ARGS_MISSING_OPTION_VALUE",
|
|
153
|
+
message: (err) => `Missing required argument for option '${err.option}'.`
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
match: (err) => _CLIPrinter.isCLIError(err) && err.code === "ERR_PARSE_ARGS_INVALID_OPTION_VALUE",
|
|
157
|
+
message: (err) => `Invalid argument'${err.message}'.`
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
match: (err) => _CLIPrinter.isCLIError(err) && err.code === "ENOENT",
|
|
161
|
+
message: (err) => `File or directory not found: ${err.message}`
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
match: () => true,
|
|
165
|
+
// fallback
|
|
166
|
+
message: (err) => _CLIPrinter.isCLIError(err) ? err.message : String(err)
|
|
167
|
+
}
|
|
168
|
+
];
|
|
169
|
+
CLIPrinter = _CLIPrinter;
|
|
170
|
+
printer_default = CLIPrinter;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
49
173
|
|
|
50
174
|
// src/logger.ts
|
|
51
|
-
var import_winston = __toESM(require("winston"), 1);
|
|
52
|
-
var import_fs = __toESM(require("fs"), 1);
|
|
53
|
-
var import_path = __toESM(require("path"), 1);
|
|
54
|
-
var import_pinggy = require("@pinggy/pinggy");
|
|
55
|
-
var _logger = null;
|
|
56
175
|
function getLogger() {
|
|
57
176
|
if (!_logger) {
|
|
58
177
|
_logger = import_winston.default.createLogger({ level: "info", silent: true });
|
|
59
178
|
}
|
|
60
179
|
return _logger;
|
|
61
180
|
}
|
|
62
|
-
var logger = getLogger();
|
|
63
181
|
function applyLoggingConfig(cfg) {
|
|
64
182
|
const {
|
|
65
183
|
level,
|
|
@@ -73,8 +191,8 @@ function applyLoggingConfig(cfg) {
|
|
|
73
191
|
enableLoggingByLogLevelInSdk(level ?? "info", filePath);
|
|
74
192
|
}
|
|
75
193
|
if (filePath) {
|
|
76
|
-
const dir =
|
|
77
|
-
if (!
|
|
194
|
+
const dir = import_path2.default.dirname(filePath);
|
|
195
|
+
if (!import_fs2.default.existsSync(dir)) import_fs2.default.mkdirSync(dir, { recursive: true });
|
|
78
196
|
}
|
|
79
197
|
const transports = [];
|
|
80
198
|
if (stdout) {
|
|
@@ -142,1126 +260,1044 @@ function enableLoggingByLogLevelInSdk(loglevel, logFilePath) {
|
|
|
142
260
|
import_pinggy.pinggy.setDebugLogging(true, import_pinggy.LogLevel.INFO, logFilePath);
|
|
143
261
|
}
|
|
144
262
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
var spinners = {
|
|
157
|
-
dots: {
|
|
158
|
-
interval: 80,
|
|
159
|
-
frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"]
|
|
263
|
+
var import_winston, import_fs2, import_path2, import_pinggy, _logger, logger;
|
|
264
|
+
var init_logger = __esm({
|
|
265
|
+
"src/logger.ts"() {
|
|
266
|
+
"use strict";
|
|
267
|
+
init_cjs_shims();
|
|
268
|
+
import_winston = __toESM(require("winston"), 1);
|
|
269
|
+
import_fs2 = __toESM(require("fs"), 1);
|
|
270
|
+
import_path2 = __toESM(require("path"), 1);
|
|
271
|
+
import_pinggy = require("@pinggy/pinggy");
|
|
272
|
+
_logger = null;
|
|
273
|
+
logger = getLogger();
|
|
160
274
|
}
|
|
161
|
-
};
|
|
162
|
-
var currentTimer = null;
|
|
163
|
-
var currentText = "";
|
|
164
|
-
function startSpinner(name = "dots", text = "Loading") {
|
|
165
|
-
const spinner = spinners[name];
|
|
166
|
-
let i = 0;
|
|
167
|
-
currentText = text;
|
|
168
|
-
if (currentTimer) {
|
|
169
|
-
clearInterval(currentTimer);
|
|
170
|
-
}
|
|
171
|
-
currentTimer = setInterval(() => {
|
|
172
|
-
const frame = spinner.frames[i = ++i % spinner.frames.length];
|
|
173
|
-
process.stdout.write(`\r${import_picocolors.default.cyan(frame)} ${text}`);
|
|
174
|
-
}, spinner.interval);
|
|
175
|
-
return () => stopSpinner();
|
|
176
|
-
}
|
|
177
|
-
function stopSpinner() {
|
|
178
|
-
if (currentTimer) {
|
|
179
|
-
clearInterval(currentTimer);
|
|
180
|
-
currentTimer = null;
|
|
181
|
-
process.stdout.write("\r\x1B[K");
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
function stopSpinnerSuccess(message) {
|
|
185
|
-
if (currentTimer) {
|
|
186
|
-
clearInterval(currentTimer);
|
|
187
|
-
currentTimer = null;
|
|
188
|
-
const finalMessage = message || currentText;
|
|
189
|
-
process.stdout.write(`\r${import_picocolors.default.green("\u2714")} ${finalMessage}
|
|
190
|
-
`);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
function stopSpinnerFail(message) {
|
|
194
|
-
if (currentTimer) {
|
|
195
|
-
clearInterval(currentTimer);
|
|
196
|
-
currentTimer = null;
|
|
197
|
-
const finalMessage = message || currentText;
|
|
198
|
-
process.stdout.write(`\r${import_picocolors.default.red("\u2716")} ${finalMessage}
|
|
199
|
-
`);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// src/utils/printer.ts
|
|
204
|
-
var _CLIPrinter = class _CLIPrinter {
|
|
205
|
-
static isCLIError(err) {
|
|
206
|
-
return err instanceof Error;
|
|
207
|
-
}
|
|
208
|
-
static print(message, ...args) {
|
|
209
|
-
console.log(message, ...args);
|
|
210
|
-
}
|
|
211
|
-
static error(err) {
|
|
212
|
-
const def = this.errorDefinitions.find((d) => d.match(err));
|
|
213
|
-
const msg = def.message(err);
|
|
214
|
-
console.error(import_picocolors2.default.red(import_picocolors2.default.bold("\u2716 Error:")), import_picocolors2.default.red(msg));
|
|
215
|
-
process.exit(1);
|
|
216
|
-
}
|
|
217
|
-
static warn(message) {
|
|
218
|
-
console.warn(import_picocolors2.default.yellow(import_picocolors2.default.bold("\u26A0 Warning:")), import_picocolors2.default.yellow(message));
|
|
219
|
-
}
|
|
220
|
-
static warnTxt(message) {
|
|
221
|
-
console.warn(import_picocolors2.default.yellow(import_picocolors2.default.bold("\u26A0 Warning:")), import_picocolors2.default.yellow(message));
|
|
222
|
-
}
|
|
223
|
-
static success(message) {
|
|
224
|
-
console.log(import_picocolors2.default.green(import_picocolors2.default.bold(" \u2714 Success:")), import_picocolors2.default.green(message));
|
|
225
|
-
}
|
|
226
|
-
static async info(message) {
|
|
227
|
-
console.log(import_picocolors2.default.blue(message));
|
|
228
|
-
}
|
|
229
|
-
static startSpinner(message) {
|
|
230
|
-
startSpinner("dots", message);
|
|
231
|
-
}
|
|
232
|
-
static stopSpinnerSuccess(message) {
|
|
233
|
-
stopSpinnerSuccess(message);
|
|
234
|
-
}
|
|
235
|
-
static stopSpinnerFail(message) {
|
|
236
|
-
stopSpinnerFail(message);
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
_CLIPrinter.errorDefinitions = [
|
|
240
|
-
{
|
|
241
|
-
match: (err) => _CLIPrinter.isCLIError(err) && err.code === "ERR_PARSE_ARGS_UNKNOWN_OPTION",
|
|
242
|
-
message: (err) => {
|
|
243
|
-
const match = /Unknown option '(.+?)'/.exec(err.message);
|
|
244
|
-
const option = match ? match[1] : "(unknown)";
|
|
245
|
-
return `Unknown option '${option}'. Please check your command or use pinggy --h for guidance.`;
|
|
246
|
-
}
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
match: (err) => _CLIPrinter.isCLIError(err) && err.code === "ERR_PARSE_ARGS_MISSING_OPTION_VALUE",
|
|
250
|
-
message: (err) => `Missing required argument for option '${err.option}'.`
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
match: (err) => _CLIPrinter.isCLIError(err) && err.code === "ERR_PARSE_ARGS_INVALID_OPTION_VALUE",
|
|
254
|
-
message: (err) => `Invalid argument'${err.message}'.`
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
match: (err) => _CLIPrinter.isCLIError(err) && err.code === "ENOENT",
|
|
258
|
-
message: (err) => `File or directory not found: ${err.message}`
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
match: () => true,
|
|
262
|
-
// fallback
|
|
263
|
-
message: (err) => _CLIPrinter.isCLIError(err) ? err.message : String(err)
|
|
264
|
-
}
|
|
265
|
-
];
|
|
266
|
-
var CLIPrinter = _CLIPrinter;
|
|
267
|
-
var printer_default = CLIPrinter;
|
|
275
|
+
});
|
|
268
276
|
|
|
269
277
|
// src/utils/util.ts
|
|
270
|
-
var import_module = require("module");
|
|
271
|
-
var import_crypto = require("crypto");
|
|
272
278
|
function getRandomId() {
|
|
273
279
|
return (0, import_crypto.randomUUID)();
|
|
274
280
|
}
|
|
275
281
|
function isValidPort(p) {
|
|
276
282
|
return Number.isInteger(p) && p > 0 && p < 65536;
|
|
277
283
|
}
|
|
278
|
-
var require2 = (0, import_module.createRequire)(importMetaUrl);
|
|
279
|
-
var pkg = require2("../package.json");
|
|
280
284
|
function getVersion() {
|
|
281
285
|
return pkg.version ?? "";
|
|
282
286
|
}
|
|
287
|
+
var import_module, import_crypto, require2, pkg;
|
|
288
|
+
var init_util = __esm({
|
|
289
|
+
"src/utils/util.ts"() {
|
|
290
|
+
"use strict";
|
|
291
|
+
init_cjs_shims();
|
|
292
|
+
import_module = require("module");
|
|
293
|
+
import_crypto = require("crypto");
|
|
294
|
+
require2 = (0, import_module.createRequire)(importMetaUrl);
|
|
295
|
+
pkg = require2("../package.json");
|
|
296
|
+
}
|
|
297
|
+
});
|
|
283
298
|
|
|
284
299
|
// src/tunnel_manager/TunnelManager.ts
|
|
285
|
-
var
|
|
286
|
-
var
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
* @throws {Error} When configId is invalid or empty
|
|
314
|
-
* @throws {Error} When a tunnel with the given configId already exists
|
|
315
|
-
*
|
|
316
|
-
* @returns {ManagedTunnel} A new managed tunnel instance containing the tunnel details,
|
|
317
|
-
* status information, and statistics
|
|
318
|
-
*/
|
|
319
|
-
async createTunnel(config) {
|
|
320
|
-
const { configid, additionalForwarding, tunnelName } = config;
|
|
321
|
-
if (configid === void 0 || configid.trim().length === 0) {
|
|
322
|
-
throw new Error(`Invalid configId: "${configid}"`);
|
|
323
|
-
}
|
|
324
|
-
if (this.tunnelsByConfigId.has(configid)) {
|
|
325
|
-
throw new Error(`Tunnel with configId "${configid}" already exists`);
|
|
326
|
-
}
|
|
327
|
-
const tunnelid = config.tunnelid || getRandomId();
|
|
328
|
-
const configWithForwarding = this.buildPinggyConfig(config, additionalForwarding);
|
|
329
|
-
return this._createTunnelWithProcessedConfig({
|
|
330
|
-
configid,
|
|
331
|
-
tunnelid,
|
|
332
|
-
tunnelName,
|
|
333
|
-
originalConfig: config,
|
|
334
|
-
configWithForwarding,
|
|
335
|
-
additionalForwarding,
|
|
336
|
-
serve: config.serve,
|
|
337
|
-
autoReconnect: config.autoReconnect !== void 0 ? config.autoReconnect : false
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* Internal method to create a tunnel with an already-processed configuration.
|
|
342
|
-
* This is used by createTunnel, restartTunnel, and updateConfig to avoid config processing.
|
|
343
|
-
*
|
|
344
|
-
* @param params - Configuration parameters with already-processed forwarding rules
|
|
345
|
-
* @returns The created ManagedTunnel instance
|
|
346
|
-
* @private
|
|
347
|
-
*/
|
|
348
|
-
async _createTunnelWithProcessedConfig(params) {
|
|
349
|
-
let instance;
|
|
350
|
-
try {
|
|
351
|
-
instance = await import_pinggy2.pinggy.createTunnel(params.configWithForwarding);
|
|
352
|
-
} catch (e) {
|
|
353
|
-
logger.error("Error creating tunnel instance:", e);
|
|
354
|
-
throw e;
|
|
355
|
-
}
|
|
356
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
357
|
-
const managed = {
|
|
358
|
-
tunnelid: params.tunnelid,
|
|
359
|
-
configid: params.configid,
|
|
360
|
-
tunnelName: params.tunnelName,
|
|
361
|
-
instance,
|
|
362
|
-
tunnelConfig: params.originalConfig,
|
|
363
|
-
configWithForwarding: params.configWithForwarding,
|
|
364
|
-
additionalForwarding: params.additionalForwarding,
|
|
365
|
-
serve: params.serve,
|
|
366
|
-
warnings: [],
|
|
367
|
-
isStopped: false,
|
|
368
|
-
createdAt: now,
|
|
369
|
-
startedAt: null,
|
|
370
|
-
stoppedAt: null,
|
|
371
|
-
autoReconnect: params.autoReconnect
|
|
372
|
-
};
|
|
373
|
-
instance.setTunnelEstablishedCallback(({}) => {
|
|
374
|
-
managed.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
375
|
-
});
|
|
376
|
-
this.setupStatsCallback(params.tunnelid, managed);
|
|
377
|
-
this.setupErrorCallback(params.tunnelid, managed);
|
|
378
|
-
this.setupDisconnectCallback(params.tunnelid, managed);
|
|
379
|
-
this.setUpTunnelWorkerErrorCallback(params.tunnelid, managed);
|
|
380
|
-
this.tunnelsByTunnelId.set(params.tunnelid, managed);
|
|
381
|
-
this.tunnelsByConfigId.set(params.configid, managed);
|
|
382
|
-
logger.info("Tunnel created", { configid: params.configid, tunnelid: params.tunnelid });
|
|
383
|
-
return managed;
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* Builds the Pinggy configuration by merging the default forwarding rule
|
|
387
|
-
* with additional forwarding rules from additionalForwarding array.
|
|
388
|
-
*
|
|
389
|
-
* @param config - The base Pinggy configuration
|
|
390
|
-
* @param additionalForwarding - Optional array of additional forwarding rules
|
|
391
|
-
* @returns Modified PinggyOptions
|
|
392
|
-
*/
|
|
393
|
-
buildPinggyConfig(config, additionalForwarding) {
|
|
394
|
-
const forwardingRules = [];
|
|
395
|
-
if (config.forwarding) {
|
|
396
|
-
forwardingRules.push({
|
|
397
|
-
type: config.tunnelType && config.tunnelType[0] || "http",
|
|
398
|
-
address: config.forwarding
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
if (Array.isArray(additionalForwarding) && additionalForwarding.length > 0) {
|
|
402
|
-
for (const rule of additionalForwarding) {
|
|
403
|
-
if (rule && rule.localDomain && rule.localPort && rule.remoteDomain && isValidPort(rule.localPort)) {
|
|
404
|
-
const forwardingRule = {
|
|
405
|
-
type: rule.protocol,
|
|
406
|
-
// In Future we can make this dynamic based on user input
|
|
407
|
-
address: `${rule.localDomain}:${rule.localPort}`,
|
|
408
|
-
listenAddress: rule.remotePort && isValidPort(rule.remotePort) ? `${rule.remoteDomain}:${rule.remotePort}` : rule.remoteDomain
|
|
409
|
-
};
|
|
410
|
-
forwardingRules.push(forwardingRule);
|
|
300
|
+
var import_pinggy2, import_node_path, import_node_worker_threads, import_node_url, __filename2, __dirname, TunnelManager;
|
|
301
|
+
var init_TunnelManager = __esm({
|
|
302
|
+
"src/tunnel_manager/TunnelManager.ts"() {
|
|
303
|
+
"use strict";
|
|
304
|
+
init_cjs_shims();
|
|
305
|
+
import_pinggy2 = require("@pinggy/pinggy");
|
|
306
|
+
init_logger();
|
|
307
|
+
import_node_path = __toESM(require("path"), 1);
|
|
308
|
+
import_node_worker_threads = require("worker_threads");
|
|
309
|
+
import_node_url = require("url");
|
|
310
|
+
init_printer();
|
|
311
|
+
init_util();
|
|
312
|
+
__filename2 = (0, import_node_url.fileURLToPath)(importMetaUrl);
|
|
313
|
+
__dirname = import_node_path.default.dirname(__filename2);
|
|
314
|
+
TunnelManager = class _TunnelManager {
|
|
315
|
+
constructor() {
|
|
316
|
+
this.tunnelsByTunnelId = /* @__PURE__ */ new Map();
|
|
317
|
+
this.tunnelsByConfigId = /* @__PURE__ */ new Map();
|
|
318
|
+
this.tunnelStats = /* @__PURE__ */ new Map();
|
|
319
|
+
this.tunnelStatsListeners = /* @__PURE__ */ new Map();
|
|
320
|
+
this.tunnelErrorListeners = /* @__PURE__ */ new Map();
|
|
321
|
+
this.tunnelDisconnectListeners = /* @__PURE__ */ new Map();
|
|
322
|
+
this.tunnelWorkerErrorListeners = /* @__PURE__ */ new Map();
|
|
323
|
+
this.tunnelStartListeners = /* @__PURE__ */ new Map();
|
|
324
|
+
}
|
|
325
|
+
static getInstance() {
|
|
326
|
+
if (!_TunnelManager.instance) {
|
|
327
|
+
_TunnelManager.instance = new _TunnelManager();
|
|
411
328
|
}
|
|
329
|
+
return _TunnelManager.instance;
|
|
412
330
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
this.startStaticFileServer(managed);
|
|
436
|
-
}
|
|
437
|
-
try {
|
|
438
|
-
const startListeners = this.tunnelStartListeners.get(tunnelId);
|
|
439
|
-
if (startListeners) {
|
|
440
|
-
for (const [id, listener] of startListeners) {
|
|
441
|
-
try {
|
|
442
|
-
listener(tunnelId, urls);
|
|
443
|
-
} catch (err) {
|
|
444
|
-
logger.debug("Error in start-listener callback", { listenerId: id, tunnelId, err });
|
|
445
|
-
}
|
|
331
|
+
/**
|
|
332
|
+
* Creates a new managed tunnel instance with the given configuration.
|
|
333
|
+
* Builds the config with forwarding rules and creates the tunnel instance.
|
|
334
|
+
*
|
|
335
|
+
* @param config - The tunnel configuration options
|
|
336
|
+
* @param config.configid - Unique identifier for the tunnel configuration
|
|
337
|
+
* @param config.tunnelid - Optional custom tunnel identifier. If not provided, a random UUID will be generated
|
|
338
|
+
* @param config.additionalForwarding - Optional array of additional forwarding configurations
|
|
339
|
+
*
|
|
340
|
+
* @throws {Error} When configId is invalid or empty
|
|
341
|
+
* @throws {Error} When a tunnel with the given configId already exists
|
|
342
|
+
*
|
|
343
|
+
* @returns {ManagedTunnel} A new managed tunnel instance containing the tunnel details,
|
|
344
|
+
* status information, and statistics
|
|
345
|
+
*/
|
|
346
|
+
async createTunnel(config) {
|
|
347
|
+
const { configid, additionalForwarding, tunnelName } = config;
|
|
348
|
+
if (configid === void 0 || configid.trim().length === 0) {
|
|
349
|
+
throw new Error(`Invalid configId: "${configid}"`);
|
|
350
|
+
}
|
|
351
|
+
if (this.tunnelsByConfigId.has(configid)) {
|
|
352
|
+
throw new Error(`Tunnel with configId "${configid}" already exists`);
|
|
446
353
|
}
|
|
354
|
+
const tunnelid = config.tunnelid || getRandomId();
|
|
355
|
+
const configWithForwarding = this.buildPinggyConfig(config, additionalForwarding);
|
|
356
|
+
return this._createTunnelWithProcessedConfig({
|
|
357
|
+
configid,
|
|
358
|
+
tunnelid,
|
|
359
|
+
tunnelName,
|
|
360
|
+
originalConfig: config,
|
|
361
|
+
configWithForwarding,
|
|
362
|
+
additionalForwarding,
|
|
363
|
+
serve: config.serve,
|
|
364
|
+
autoReconnect: config.autoReconnect !== void 0 ? config.autoReconnect : false
|
|
365
|
+
});
|
|
447
366
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
367
|
+
/**
|
|
368
|
+
* Internal method to create a tunnel with an already-processed configuration.
|
|
369
|
+
* This is used by createTunnel, restartTunnel, and updateConfig to avoid config processing.
|
|
370
|
+
*
|
|
371
|
+
* @param params - Configuration parameters with already-processed forwarding rules
|
|
372
|
+
* @returns The created ManagedTunnel instance
|
|
373
|
+
* @private
|
|
374
|
+
*/
|
|
375
|
+
async _createTunnelWithProcessedConfig(params) {
|
|
376
|
+
let instance;
|
|
377
|
+
try {
|
|
378
|
+
instance = await import_pinggy2.pinggy.createTunnel(params.configWithForwarding);
|
|
379
|
+
} catch (e) {
|
|
380
|
+
logger.error("Error creating tunnel instance:", e);
|
|
381
|
+
throw e;
|
|
382
|
+
}
|
|
383
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
384
|
+
const managed = {
|
|
385
|
+
tunnelid: params.tunnelid,
|
|
386
|
+
configid: params.configid,
|
|
387
|
+
tunnelName: params.tunnelName,
|
|
388
|
+
instance,
|
|
389
|
+
tunnelConfig: params.originalConfig,
|
|
390
|
+
configWithForwarding: params.configWithForwarding,
|
|
391
|
+
additionalForwarding: params.additionalForwarding,
|
|
392
|
+
serve: params.serve,
|
|
393
|
+
warnings: [],
|
|
394
|
+
isStopped: false,
|
|
395
|
+
createdAt: now,
|
|
396
|
+
startedAt: null,
|
|
397
|
+
stoppedAt: null,
|
|
398
|
+
autoReconnect: params.autoReconnect
|
|
399
|
+
};
|
|
400
|
+
instance.setTunnelEstablishedCallback(({}) => {
|
|
401
|
+
managed.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
402
|
+
});
|
|
403
|
+
this.setupStatsCallback(params.tunnelid, managed);
|
|
404
|
+
this.setupErrorCallback(params.tunnelid, managed);
|
|
405
|
+
this.setupDisconnectCallback(params.tunnelid, managed);
|
|
406
|
+
this.setUpTunnelWorkerErrorCallback(params.tunnelid, managed);
|
|
407
|
+
this.tunnelsByTunnelId.set(params.tunnelid, managed);
|
|
408
|
+
this.tunnelsByConfigId.set(params.configid, managed);
|
|
409
|
+
logger.info("Tunnel created", { configid: params.configid, tunnelid: params.tunnelid });
|
|
410
|
+
return managed;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Builds the Pinggy configuration by merging the default forwarding rule
|
|
414
|
+
* with additional forwarding rules from additionalForwarding array.
|
|
415
|
+
*
|
|
416
|
+
* @param config - The base Pinggy configuration
|
|
417
|
+
* @param additionalForwarding - Optional array of additional forwarding rules
|
|
418
|
+
* @returns Modified PinggyOptions
|
|
419
|
+
*/
|
|
420
|
+
buildPinggyConfig(config, additionalForwarding) {
|
|
421
|
+
const forwardingRules = [];
|
|
422
|
+
if (config.forwarding) {
|
|
423
|
+
forwardingRules.push({
|
|
424
|
+
type: config.tunnelType && config.tunnelType[0] || "http",
|
|
425
|
+
address: config.forwarding
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
if (Array.isArray(additionalForwarding) && additionalForwarding.length > 0) {
|
|
429
|
+
for (const rule of additionalForwarding) {
|
|
430
|
+
if (rule && rule.localDomain && rule.localPort && rule.remoteDomain && isValidPort(rule.localPort)) {
|
|
431
|
+
const forwardingRule = {
|
|
432
|
+
type: rule.protocol,
|
|
433
|
+
// In Future we can make this dynamic based on user input
|
|
434
|
+
address: `${rule.localDomain}:${rule.localPort}`,
|
|
435
|
+
listenAddress: rule.remotePort && isValidPort(rule.remotePort) ? `${rule.remoteDomain}:${rule.remotePort}` : rule.remoteDomain
|
|
436
|
+
};
|
|
437
|
+
forwardingRules.push(forwardingRule);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
517
441
|
return {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
tunnelName: tunnel.tunnelName,
|
|
521
|
-
tunnelConfig: tunnel.tunnelConfig,
|
|
522
|
-
remoteurls: !tunnel.isStopped ? await this.getTunnelUrls(tunnel.tunnelid) : [],
|
|
523
|
-
additionalForwarding: tunnel.additionalForwarding,
|
|
524
|
-
serve: tunnel.serve
|
|
442
|
+
...config,
|
|
443
|
+
forwarding: forwardingRules.length > 0 ? forwardingRules : config.forwarding
|
|
525
444
|
};
|
|
526
|
-
}));
|
|
527
|
-
return tunnelList;
|
|
528
|
-
} catch (err) {
|
|
529
|
-
logger.error("Error fetching tunnels", { error: err });
|
|
530
|
-
return [];
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
/**
|
|
534
|
-
* Get status of a tunnel
|
|
535
|
-
*/
|
|
536
|
-
async getTunnelStatus(tunnelId) {
|
|
537
|
-
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
538
|
-
if (!managed) {
|
|
539
|
-
logger.error(`Tunnel "${tunnelId}" not found when fetching status`);
|
|
540
|
-
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
541
|
-
}
|
|
542
|
-
if (managed.isStopped) {
|
|
543
|
-
return "exited";
|
|
544
|
-
}
|
|
545
|
-
const status = await managed.instance.getStatus();
|
|
546
|
-
logger.debug("Queried tunnel status", { tunnelId, status });
|
|
547
|
-
return status;
|
|
548
|
-
}
|
|
549
|
-
/**
|
|
550
|
-
* Stop all tunnels
|
|
551
|
-
*/
|
|
552
|
-
stopAllTunnels() {
|
|
553
|
-
for (const { instance } of this.tunnelsByTunnelId.values()) {
|
|
554
|
-
try {
|
|
555
|
-
instance.stop();
|
|
556
|
-
} catch (e) {
|
|
557
|
-
logger.warn("Error stopping tunnel instance", e);
|
|
558
445
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
removeStoppedTunnelByConfigId(configId) {
|
|
593
|
-
const managed = this.tunnelsByConfigId.get(configId);
|
|
594
|
-
if (!managed) {
|
|
595
|
-
logger.debug("Attempted to remove non-existent tunnel by configId", { configId });
|
|
596
|
-
return false;
|
|
597
|
-
}
|
|
598
|
-
return this.removeStoppedTunnelByTunnelId(managed.tunnelid);
|
|
599
|
-
}
|
|
600
|
-
_cleanupTunnelRecords(managed) {
|
|
601
|
-
if (!managed.isStopped) {
|
|
602
|
-
throw new Error(`Active tunnel "${managed.tunnelid}" cannot be removed`);
|
|
603
|
-
}
|
|
604
|
-
try {
|
|
605
|
-
if (managed.serveWorker) {
|
|
606
|
-
managed.serveWorker = null;
|
|
607
|
-
}
|
|
608
|
-
this.tunnelStats.delete(managed.tunnelid);
|
|
609
|
-
this.tunnelStatsListeners.delete(managed.tunnelid);
|
|
610
|
-
this.tunnelErrorListeners.delete(managed.tunnelid);
|
|
611
|
-
this.tunnelDisconnectListeners.delete(managed.tunnelid);
|
|
612
|
-
this.tunnelWorkerErrorListeners.delete(managed.tunnelid);
|
|
613
|
-
this.tunnelStartListeners.delete(managed.tunnelid);
|
|
614
|
-
this.tunnelsByTunnelId.delete(managed.tunnelid);
|
|
615
|
-
this.tunnelsByConfigId.delete(managed.configid);
|
|
616
|
-
} catch (e) {
|
|
617
|
-
logger.warn("Failed cleaning up tunnel records", { tunnelId: managed.tunnelid, error: e });
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
/**
|
|
621
|
-
* Get tunnel instance by either configId or tunnelId
|
|
622
|
-
* @param configId - The configuration ID of the tunnel
|
|
623
|
-
* @param tunnelId - The tunnel ID
|
|
624
|
-
* @returns The tunnel instance
|
|
625
|
-
* @throws Error if neither configId nor tunnelId is provided, or if tunnel is not found
|
|
626
|
-
*/
|
|
627
|
-
getTunnelInstance(configId, tunnelId) {
|
|
628
|
-
if (configId) {
|
|
629
|
-
const managed = this.tunnelsByConfigId.get(configId);
|
|
630
|
-
if (!managed) throw new Error(`Tunnel "${configId}" not found`);
|
|
631
|
-
return managed.instance;
|
|
632
|
-
}
|
|
633
|
-
if (tunnelId) {
|
|
634
|
-
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
635
|
-
if (!managed) throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
636
|
-
return managed.instance;
|
|
637
|
-
}
|
|
638
|
-
throw new Error(`Either configId or tunnelId must be provided`);
|
|
639
|
-
}
|
|
640
|
-
/**
|
|
641
|
-
* Get tunnel config by either configId or tunnelId
|
|
642
|
-
* @param configId - The configuration ID of the tunnel
|
|
643
|
-
* @param tunnelId - The tunnel ID
|
|
644
|
-
* @returns The tunnel config
|
|
645
|
-
* @throws Error if neither configId nor tunnelId is provided, or if tunnel is not found
|
|
646
|
-
*/
|
|
647
|
-
async getTunnelConfig(configId, tunnelId) {
|
|
648
|
-
if (configId) {
|
|
649
|
-
const managed = this.tunnelsByConfigId.get(configId);
|
|
650
|
-
if (!managed) {
|
|
651
|
-
throw new Error(`Tunnel with configId "${configId}" not found`);
|
|
652
|
-
}
|
|
653
|
-
return managed.instance.getConfig();
|
|
654
|
-
}
|
|
655
|
-
if (tunnelId) {
|
|
656
|
-
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
657
|
-
if (!managed) {
|
|
658
|
-
throw new Error(`Tunnel with tunnelId "${tunnelId}" not found`);
|
|
446
|
+
/**
|
|
447
|
+
* Start a tunnel that was created but not yet started
|
|
448
|
+
*/
|
|
449
|
+
async startTunnel(tunnelId) {
|
|
450
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
451
|
+
if (!managed) throw new Error(`Tunnel with id "${tunnelId}" not found`);
|
|
452
|
+
logger.info("Starting tunnel", { tunnelId });
|
|
453
|
+
let urls;
|
|
454
|
+
try {
|
|
455
|
+
urls = await managed.instance.start();
|
|
456
|
+
} catch (error) {
|
|
457
|
+
logger.error("Failed to start tunnel", { tunnelId, error });
|
|
458
|
+
throw error;
|
|
459
|
+
}
|
|
460
|
+
logger.info("Tunnel started", { tunnelId, urls });
|
|
461
|
+
if (managed.serve) {
|
|
462
|
+
this.startStaticFileServer(managed);
|
|
463
|
+
}
|
|
464
|
+
try {
|
|
465
|
+
const startListeners = this.tunnelStartListeners.get(tunnelId);
|
|
466
|
+
if (startListeners) {
|
|
467
|
+
for (const [id, listener] of startListeners) {
|
|
468
|
+
try {
|
|
469
|
+
listener(tunnelId, urls);
|
|
470
|
+
} catch (err) {
|
|
471
|
+
logger.debug("Error in start-listener callback", { listenerId: id, tunnelId, err });
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
} catch (e) {
|
|
476
|
+
logger.warn("Failed to notify start listeners", { tunnelId, e });
|
|
477
|
+
}
|
|
478
|
+
return urls;
|
|
659
479
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
originalConfig: currentConfig,
|
|
699
|
-
configWithForwarding,
|
|
700
|
-
additionalForwarding,
|
|
701
|
-
serve: currentServe,
|
|
702
|
-
autoReconnect
|
|
703
|
-
});
|
|
704
|
-
if (existingTunnel.createdAt) {
|
|
705
|
-
newTunnel.createdAt = existingTunnel.createdAt;
|
|
480
|
+
/**
|
|
481
|
+
* Stops a running tunnel and updates its status.
|
|
482
|
+
*
|
|
483
|
+
* @param tunnelId - The unique identifier of the tunnel to stop
|
|
484
|
+
* @throws {Error} If the tunnel with the given tunnelId is not found
|
|
485
|
+
* @remarks
|
|
486
|
+
* - Clears the tunnel's remote URLs
|
|
487
|
+
* - Updates the tunnel's state to Exited if stopped successfully
|
|
488
|
+
* - Logs the stop operation with tunnelId and configId
|
|
489
|
+
*/
|
|
490
|
+
stopTunnel(tunnelId) {
|
|
491
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
492
|
+
if (!managed) throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
493
|
+
logger.info("Stopping tunnel", { tunnelId, configId: managed.configid });
|
|
494
|
+
try {
|
|
495
|
+
managed.instance.stop();
|
|
496
|
+
if (managed.serveWorker) {
|
|
497
|
+
logger.info("terminating serveWorker");
|
|
498
|
+
managed.serveWorker.terminate();
|
|
499
|
+
}
|
|
500
|
+
this.tunnelStats.delete(tunnelId);
|
|
501
|
+
this.tunnelStatsListeners.delete(tunnelId);
|
|
502
|
+
this.tunnelStats.delete(tunnelId);
|
|
503
|
+
this.tunnelStatsListeners.delete(tunnelId);
|
|
504
|
+
this.tunnelErrorListeners.delete(tunnelId);
|
|
505
|
+
this.tunnelDisconnectListeners.delete(tunnelId);
|
|
506
|
+
this.tunnelWorkerErrorListeners.delete(tunnelId);
|
|
507
|
+
this.tunnelStartListeners.delete(tunnelId);
|
|
508
|
+
managed.serveWorker = null;
|
|
509
|
+
managed.warnings = managed.warnings ?? [];
|
|
510
|
+
managed.isStopped = true;
|
|
511
|
+
managed.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
512
|
+
logger.info("Tunnel stopped", { tunnelId, configId: managed.configid });
|
|
513
|
+
return { configid: managed.configid, tunnelid: managed.tunnelid };
|
|
514
|
+
} catch (error) {
|
|
515
|
+
logger.error("Failed to stop tunnel", { tunnelId, error });
|
|
516
|
+
throw error;
|
|
517
|
+
}
|
|
706
518
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
*
|
|
725
|
-
* @returns Promise resolving to the updated ManagedTunnel
|
|
726
|
-
* @throws Error if the tunnel is not found or if the update process fails
|
|
727
|
-
*/
|
|
728
|
-
async updateConfig(newConfig) {
|
|
729
|
-
const { configid, tunnelName: newTunnelName, additionalForwarding } = newConfig;
|
|
730
|
-
if (!configid || configid.trim().length === 0) {
|
|
731
|
-
throw new Error(`Invalid configid: "${configid}"`);
|
|
732
|
-
}
|
|
733
|
-
const existingTunnel = this.tunnelsByConfigId.get(configid);
|
|
734
|
-
if (!existingTunnel) {
|
|
735
|
-
throw new Error(`Tunnel with config id "${configid}" not found`);
|
|
736
|
-
}
|
|
737
|
-
const isStopped = existingTunnel.isStopped;
|
|
738
|
-
const currentTunnelConfig = existingTunnel.tunnelConfig;
|
|
739
|
-
const currentConfigWithForwarding = existingTunnel.configWithForwarding;
|
|
740
|
-
const currentTunnelId = existingTunnel.tunnelid;
|
|
741
|
-
const currentTunnelConfigId = existingTunnel.configid;
|
|
742
|
-
const currentAdditionalForwarding = existingTunnel.additionalForwarding;
|
|
743
|
-
const currentTunnelName = existingTunnel.tunnelName;
|
|
744
|
-
const currentServe = existingTunnel.serve;
|
|
745
|
-
const currentAutoReconnect = existingTunnel.autoReconnect || false;
|
|
746
|
-
try {
|
|
747
|
-
if (!isStopped) {
|
|
748
|
-
existingTunnel.instance.stop();
|
|
749
|
-
}
|
|
750
|
-
this.tunnelsByTunnelId.delete(currentTunnelId);
|
|
751
|
-
this.tunnelsByConfigId.delete(currentTunnelConfigId);
|
|
752
|
-
const mergedBaseConfig = {
|
|
753
|
-
...newConfig,
|
|
754
|
-
configid,
|
|
755
|
-
tunnelName: newTunnelName !== void 0 ? newTunnelName : currentTunnelName,
|
|
756
|
-
serve: newConfig.serve !== void 0 ? newConfig.serve : currentServe
|
|
757
|
-
};
|
|
758
|
-
const newConfigWithForwarding = this.buildPinggyConfig(
|
|
759
|
-
mergedBaseConfig,
|
|
760
|
-
additionalForwarding !== void 0 ? additionalForwarding : currentAdditionalForwarding
|
|
761
|
-
);
|
|
762
|
-
const newTunnel = await this._createTunnelWithProcessedConfig({
|
|
763
|
-
configid,
|
|
764
|
-
tunnelid: currentTunnelId,
|
|
765
|
-
tunnelName: newTunnelName !== void 0 ? newTunnelName : currentTunnelName,
|
|
766
|
-
originalConfig: mergedBaseConfig,
|
|
767
|
-
configWithForwarding: newConfigWithForwarding,
|
|
768
|
-
additionalForwarding: additionalForwarding !== void 0 ? additionalForwarding : currentAdditionalForwarding,
|
|
769
|
-
serve: newConfig.serve !== void 0 ? newConfig.serve : currentServe,
|
|
770
|
-
autoReconnect: currentAutoReconnect
|
|
771
|
-
});
|
|
772
|
-
if (!isStopped) {
|
|
773
|
-
await this.startTunnel(newTunnel.tunnelid);
|
|
519
|
+
/**
|
|
520
|
+
* Get all public URLs for a tunnel
|
|
521
|
+
*/
|
|
522
|
+
async getTunnelUrls(tunnelId) {
|
|
523
|
+
try {
|
|
524
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
525
|
+
if (!managed || managed.isStopped) {
|
|
526
|
+
logger.error(`Tunnel "${tunnelId}" not found when fetching URLs`);
|
|
527
|
+
return [];
|
|
528
|
+
}
|
|
529
|
+
const urls = await managed.instance.urls();
|
|
530
|
+
logger.debug("Queried tunnel URLs", { tunnelId, urls });
|
|
531
|
+
return urls;
|
|
532
|
+
} catch (error) {
|
|
533
|
+
logger.error("Error fetching tunnel URLs", { tunnelId, error });
|
|
534
|
+
throw error;
|
|
535
|
+
}
|
|
774
536
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
});
|
|
797
|
-
if (!isStopped) {
|
|
798
|
-
await this.startTunnel(originalTunnel.tunnelid);
|
|
537
|
+
/**
|
|
538
|
+
* Get all TunnelStatus currently managed by this TunnelManager
|
|
539
|
+
* @returns An array of all TunnelStatus objects
|
|
540
|
+
*/
|
|
541
|
+
async getAllTunnels() {
|
|
542
|
+
try {
|
|
543
|
+
const tunnelList = await Promise.all(Array.from(this.tunnelsByTunnelId.values()).map(async (tunnel) => {
|
|
544
|
+
return {
|
|
545
|
+
tunnelid: tunnel.tunnelid,
|
|
546
|
+
configid: tunnel.configid,
|
|
547
|
+
tunnelName: tunnel.tunnelName,
|
|
548
|
+
tunnelConfig: tunnel.tunnelConfig,
|
|
549
|
+
remoteurls: !tunnel.isStopped ? await this.getTunnelUrls(tunnel.tunnelid) : [],
|
|
550
|
+
additionalForwarding: tunnel.additionalForwarding,
|
|
551
|
+
serve: tunnel.serve
|
|
552
|
+
};
|
|
553
|
+
}));
|
|
554
|
+
return tunnelList;
|
|
555
|
+
} catch (err) {
|
|
556
|
+
logger.error("Error fetching tunnels", { error: err });
|
|
557
|
+
return [];
|
|
799
558
|
}
|
|
800
|
-
logger.warn("Restored original tunnel configuration after update failure", {
|
|
801
|
-
currentTunnelId,
|
|
802
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
803
|
-
});
|
|
804
|
-
} catch (restoreError) {
|
|
805
|
-
logger.error("Failed to restore original tunnel configuration", {
|
|
806
|
-
currentTunnelId,
|
|
807
|
-
error: restoreError instanceof Error ? restoreError.message : "Unknown error"
|
|
808
|
-
});
|
|
809
559
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
if (!managed) throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
826
|
-
return managed;
|
|
827
|
-
}
|
|
828
|
-
throw new Error(`Either configId or tunnelId must be provided`);
|
|
829
|
-
}
|
|
830
|
-
async getTunnelGreetMessage(tunnelId) {
|
|
831
|
-
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
832
|
-
if (!managed) {
|
|
833
|
-
logger.error(`Tunnel "${tunnelId}" not found when fetching greet message`);
|
|
834
|
-
return null;
|
|
835
|
-
}
|
|
836
|
-
try {
|
|
837
|
-
if (managed.isStopped) {
|
|
838
|
-
return null;
|
|
560
|
+
/**
|
|
561
|
+
* Get status of a tunnel
|
|
562
|
+
*/
|
|
563
|
+
async getTunnelStatus(tunnelId) {
|
|
564
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
565
|
+
if (!managed) {
|
|
566
|
+
logger.error(`Tunnel "${tunnelId}" not found when fetching status`);
|
|
567
|
+
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
568
|
+
}
|
|
569
|
+
if (managed.isStopped) {
|
|
570
|
+
return "exited";
|
|
571
|
+
}
|
|
572
|
+
const status = await managed.instance.getStatus();
|
|
573
|
+
logger.debug("Queried tunnel status", { tunnelId, status });
|
|
574
|
+
return status;
|
|
839
575
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
576
|
+
/**
|
|
577
|
+
* Stop all tunnels
|
|
578
|
+
*/
|
|
579
|
+
stopAllTunnels() {
|
|
580
|
+
for (const { instance } of this.tunnelsByTunnelId.values()) {
|
|
581
|
+
try {
|
|
582
|
+
instance.stop();
|
|
583
|
+
} catch (e) {
|
|
584
|
+
logger.warn("Error stopping tunnel instance", e);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
this.tunnelsByTunnelId.clear();
|
|
588
|
+
this.tunnelsByConfigId.clear();
|
|
589
|
+
this.tunnelStats.clear();
|
|
590
|
+
this.tunnelStatsListeners.clear();
|
|
591
|
+
logger.info("All tunnels stopped and cleared");
|
|
843
592
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
}
|
|
865
|
-
const stats = this.tunnelStats.get(tunnelId);
|
|
866
|
-
if (stats && stats.length > 0) {
|
|
867
|
-
return stats[stats.length - 1];
|
|
868
|
-
}
|
|
869
|
-
return null;
|
|
870
|
-
}
|
|
871
|
-
/**
|
|
872
|
-
* Registers a listener function to receive tunnel statistics updates.
|
|
873
|
-
* The listener will be called whenever any tunnel's stats are updated.
|
|
874
|
-
*
|
|
875
|
-
* @param tunnelId - The tunnel ID to listen to stats for
|
|
876
|
-
* @param listener - Function that receives tunnelId and stats when updates occur
|
|
877
|
-
* @returns A unique listener ID that can be used to deregister the listener and tunnelId
|
|
878
|
-
*
|
|
879
|
-
* @throws {Error} When the specified tunnelId does not exist
|
|
880
|
-
*/
|
|
881
|
-
async registerStatsListener(tunnelId, listener) {
|
|
882
|
-
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
883
|
-
if (!managed) {
|
|
884
|
-
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
885
|
-
}
|
|
886
|
-
if (!this.tunnelStatsListeners.has(tunnelId)) {
|
|
887
|
-
this.tunnelStatsListeners.set(tunnelId, /* @__PURE__ */ new Map());
|
|
888
|
-
}
|
|
889
|
-
const listenerId = getRandomId();
|
|
890
|
-
const tunnelListeners = this.tunnelStatsListeners.get(tunnelId);
|
|
891
|
-
tunnelListeners.set(listenerId, listener);
|
|
892
|
-
logger.info("Stats listener registered for tunnel", { tunnelId, listenerId });
|
|
893
|
-
return [listenerId, tunnelId];
|
|
894
|
-
}
|
|
895
|
-
async registerErrorListener(tunnelId, listener) {
|
|
896
|
-
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
897
|
-
if (!managed) {
|
|
898
|
-
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
899
|
-
}
|
|
900
|
-
if (!this.tunnelErrorListeners.has(tunnelId)) {
|
|
901
|
-
this.tunnelErrorListeners.set(tunnelId, /* @__PURE__ */ new Map());
|
|
902
|
-
}
|
|
903
|
-
const listenerId = getRandomId();
|
|
904
|
-
const tunnelErrorListeners = this.tunnelErrorListeners.get(tunnelId);
|
|
905
|
-
tunnelErrorListeners.set(listenerId, listener);
|
|
906
|
-
logger.info("Error listener registered for tunnel", { tunnelId, listenerId });
|
|
907
|
-
return listenerId;
|
|
908
|
-
}
|
|
909
|
-
async registerDisconnectListener(tunnelId, listener) {
|
|
910
|
-
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
911
|
-
if (!managed) {
|
|
912
|
-
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
913
|
-
}
|
|
914
|
-
if (!this.tunnelDisconnectListeners.has(tunnelId)) {
|
|
915
|
-
this.tunnelDisconnectListeners.set(tunnelId, /* @__PURE__ */ new Map());
|
|
916
|
-
}
|
|
917
|
-
const listenerId = getRandomId();
|
|
918
|
-
const tunnelDisconnectListeners = this.tunnelDisconnectListeners.get(tunnelId);
|
|
919
|
-
tunnelDisconnectListeners.set(listenerId, listener);
|
|
920
|
-
logger.info("Disconnect listener registered for tunnel", { tunnelId, listenerId });
|
|
921
|
-
return listenerId;
|
|
922
|
-
}
|
|
923
|
-
async registerWorkerErrorListner(tunnelId, listener) {
|
|
924
|
-
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
925
|
-
if (!managed) {
|
|
926
|
-
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
927
|
-
}
|
|
928
|
-
if (!this.tunnelWorkerErrorListeners.has(tunnelId)) {
|
|
929
|
-
this.tunnelWorkerErrorListeners.set(tunnelId, /* @__PURE__ */ new Map());
|
|
930
|
-
}
|
|
931
|
-
const listenerId = getRandomId();
|
|
932
|
-
const tunnelWorkerErrorListner = this.tunnelWorkerErrorListeners.get(tunnelId);
|
|
933
|
-
tunnelWorkerErrorListner?.set(listenerId, listener);
|
|
934
|
-
logger.info("TunnelWorker error listener registered for tunnel", { tunnelId, listenerId });
|
|
935
|
-
}
|
|
936
|
-
async registerStartListener(tunnelId, listener) {
|
|
937
|
-
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
938
|
-
if (!managed) {
|
|
939
|
-
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
940
|
-
}
|
|
941
|
-
if (!this.tunnelStartListeners.has(tunnelId)) {
|
|
942
|
-
this.tunnelStartListeners.set(tunnelId, /* @__PURE__ */ new Map());
|
|
943
|
-
}
|
|
944
|
-
const listenerId = getRandomId();
|
|
945
|
-
const listeners = this.tunnelStartListeners.get(tunnelId);
|
|
946
|
-
listeners.set(listenerId, listener);
|
|
947
|
-
logger.info("Start listener registered for tunnel", { tunnelId, listenerId });
|
|
948
|
-
return listenerId;
|
|
949
|
-
}
|
|
950
|
-
/**
|
|
951
|
-
* Removes a previously registered stats listener.
|
|
952
|
-
*
|
|
953
|
-
* @param tunnelId - The tunnel ID the listener was registered for
|
|
954
|
-
* @param listenerId - The unique ID returned when the listener was registered
|
|
955
|
-
*/
|
|
956
|
-
deregisterStatsListener(tunnelId, listenerId) {
|
|
957
|
-
const tunnelListeners = this.tunnelStatsListeners.get(tunnelId);
|
|
958
|
-
if (!tunnelListeners) {
|
|
959
|
-
logger.warn("No listeners found for tunnel", { tunnelId });
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
|
-
const removed = tunnelListeners.delete(listenerId);
|
|
963
|
-
if (removed) {
|
|
964
|
-
logger.info("Stats listener deregistered", { tunnelId, listenerId });
|
|
965
|
-
if (tunnelListeners.size === 0) {
|
|
966
|
-
this.tunnelStatsListeners.delete(tunnelId);
|
|
593
|
+
/**
|
|
594
|
+
* Remove a stopped tunnel's records so it will no longer be returned by list methods.
|
|
595
|
+
*
|
|
596
|
+
*
|
|
597
|
+
* @param tunnelId - the tunnel id to remove
|
|
598
|
+
* @returns true if the record was removed, false otherwise
|
|
599
|
+
*/
|
|
600
|
+
removeStoppedTunnelByTunnelId(tunnelId) {
|
|
601
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
602
|
+
if (!managed) {
|
|
603
|
+
logger.debug("Attempted to remove non-existent tunnel", { tunnelId });
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
if (!managed.isStopped) {
|
|
607
|
+
logger.warn("Attempted to remove tunnel that is not stopped", { tunnelId });
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
this._cleanupTunnelRecords(managed);
|
|
611
|
+
logger.info("Removed stopped tunnel records", { tunnelId, configId: managed.configid });
|
|
612
|
+
return true;
|
|
967
613
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
logger.info("Error listener deregistered", { tunnelId, listenerId });
|
|
981
|
-
if (listeners.size === 0) {
|
|
982
|
-
this.tunnelErrorListeners.delete(tunnelId);
|
|
614
|
+
/**
|
|
615
|
+
* Remove a stopped tunnel by its config id.
|
|
616
|
+
* @param configId - the config id to remove
|
|
617
|
+
* @returns true if the record was removed, false otherwise
|
|
618
|
+
*/
|
|
619
|
+
removeStoppedTunnelByConfigId(configId) {
|
|
620
|
+
const managed = this.tunnelsByConfigId.get(configId);
|
|
621
|
+
if (!managed) {
|
|
622
|
+
logger.debug("Attempted to remove non-existent tunnel by configId", { configId });
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
return this.removeStoppedTunnelByTunnelId(managed.tunnelid);
|
|
983
626
|
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
627
|
+
_cleanupTunnelRecords(managed) {
|
|
628
|
+
if (!managed.isStopped) {
|
|
629
|
+
throw new Error(`Active tunnel "${managed.tunnelid}" cannot be removed`);
|
|
630
|
+
}
|
|
631
|
+
try {
|
|
632
|
+
if (managed.serveWorker) {
|
|
633
|
+
managed.serveWorker = null;
|
|
634
|
+
}
|
|
635
|
+
this.tunnelStats.delete(managed.tunnelid);
|
|
636
|
+
this.tunnelStatsListeners.delete(managed.tunnelid);
|
|
637
|
+
this.tunnelErrorListeners.delete(managed.tunnelid);
|
|
638
|
+
this.tunnelDisconnectListeners.delete(managed.tunnelid);
|
|
639
|
+
this.tunnelWorkerErrorListeners.delete(managed.tunnelid);
|
|
640
|
+
this.tunnelStartListeners.delete(managed.tunnelid);
|
|
641
|
+
this.tunnelsByTunnelId.delete(managed.tunnelid);
|
|
642
|
+
this.tunnelsByConfigId.delete(managed.configid);
|
|
643
|
+
} catch (e) {
|
|
644
|
+
logger.warn("Failed cleaning up tunnel records", { tunnelId: managed.tunnelid, error: e });
|
|
645
|
+
}
|
|
999
646
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
647
|
+
/**
|
|
648
|
+
* Get tunnel instance by either configId or tunnelId
|
|
649
|
+
* @param configId - The configuration ID of the tunnel
|
|
650
|
+
* @param tunnelId - The tunnel ID
|
|
651
|
+
* @returns The tunnel instance
|
|
652
|
+
* @throws Error if neither configId nor tunnelId is provided, or if tunnel is not found
|
|
653
|
+
*/
|
|
654
|
+
getTunnelInstance(configId, tunnelId) {
|
|
655
|
+
if (configId) {
|
|
656
|
+
const managed = this.tunnelsByConfigId.get(configId);
|
|
657
|
+
if (!managed) throw new Error(`Tunnel "${configId}" not found`);
|
|
658
|
+
return managed.instance;
|
|
659
|
+
}
|
|
660
|
+
if (tunnelId) {
|
|
661
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
662
|
+
if (!managed) throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
663
|
+
return managed.instance;
|
|
664
|
+
}
|
|
665
|
+
throw new Error(`Either configId or tunnelId must be provided`);
|
|
1013
666
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
667
|
+
/**
|
|
668
|
+
* Get tunnel config by either configId or tunnelId
|
|
669
|
+
* @param configId - The configuration ID of the tunnel
|
|
670
|
+
* @param tunnelId - The tunnel ID
|
|
671
|
+
* @returns The tunnel config
|
|
672
|
+
* @throws Error if neither configId nor tunnelId is provided, or if tunnel is not found
|
|
673
|
+
*/
|
|
674
|
+
async getTunnelConfig(configId, tunnelId) {
|
|
675
|
+
if (configId) {
|
|
676
|
+
const managed = this.tunnelsByConfigId.get(configId);
|
|
677
|
+
if (!managed) {
|
|
678
|
+
throw new Error(`Tunnel with configId "${configId}" not found`);
|
|
679
|
+
}
|
|
680
|
+
return managed.instance.getConfig();
|
|
681
|
+
}
|
|
682
|
+
if (tunnelId) {
|
|
683
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
684
|
+
if (!managed) {
|
|
685
|
+
throw new Error(`Tunnel with tunnelId "${tunnelId}" not found`);
|
|
686
|
+
}
|
|
687
|
+
return managed.instance.getConfig();
|
|
688
|
+
}
|
|
689
|
+
throw new Error(`Either configId or tunnelId must be provided`);
|
|
1017
690
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
};
|
|
1033
|
-
managed.instance.setUsageUpdateCallback(callback);
|
|
1034
|
-
logger.debug("Stats callback set up for tunnel", { tunnelId });
|
|
1035
|
-
} catch (error) {
|
|
1036
|
-
logger.warn("Failed to set up stats callback", { tunnelId, error });
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
notifyErrorListeners(tunnelId, errorMsg, isFatal) {
|
|
1040
|
-
try {
|
|
1041
|
-
const listeners = this.tunnelErrorListeners.get(tunnelId);
|
|
1042
|
-
if (!listeners) return;
|
|
1043
|
-
for (const [id, listener] of listeners) {
|
|
691
|
+
/**
|
|
692
|
+
* Restarts a tunnel with its current configuration.
|
|
693
|
+
* This function will stop the tunnel if it's running and start it again.
|
|
694
|
+
* All configurations including additional forwarding rules are preserved.
|
|
695
|
+
*/
|
|
696
|
+
async restartTunnel(tunnelid) {
|
|
697
|
+
const existingTunnel = this.tunnelsByTunnelId.get(tunnelid);
|
|
698
|
+
if (!existingTunnel) {
|
|
699
|
+
throw new Error(`Tunnel "${tunnelid}" not found`);
|
|
700
|
+
}
|
|
701
|
+
logger.info("Initiating tunnel restart", {
|
|
702
|
+
tunnelId: tunnelid,
|
|
703
|
+
configId: existingTunnel.configid
|
|
704
|
+
});
|
|
1044
705
|
try {
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
706
|
+
const tunnelName = existingTunnel.tunnelName;
|
|
707
|
+
const currentConfigId = existingTunnel.configid;
|
|
708
|
+
const currentConfig = existingTunnel.tunnelConfig;
|
|
709
|
+
const configWithForwarding = existingTunnel.configWithForwarding;
|
|
710
|
+
const additionalForwarding = existingTunnel.additionalForwarding;
|
|
711
|
+
const currentServe = existingTunnel.serve;
|
|
712
|
+
const autoReconnect = existingTunnel.autoReconnect || false;
|
|
713
|
+
this.tunnelsByTunnelId.delete(tunnelid);
|
|
714
|
+
this.tunnelsByConfigId.delete(existingTunnel.configid);
|
|
715
|
+
this.tunnelStats.delete(tunnelid);
|
|
716
|
+
this.tunnelStatsListeners.delete(tunnelid);
|
|
717
|
+
this.tunnelErrorListeners.delete(tunnelid);
|
|
718
|
+
this.tunnelDisconnectListeners.delete(tunnelid);
|
|
719
|
+
this.tunnelWorkerErrorListeners.delete(tunnelid);
|
|
720
|
+
this.tunnelStartListeners.delete(tunnelid);
|
|
721
|
+
const newTunnel = await this._createTunnelWithProcessedConfig({
|
|
722
|
+
configid: currentConfigId,
|
|
723
|
+
tunnelid,
|
|
724
|
+
tunnelName,
|
|
725
|
+
originalConfig: currentConfig,
|
|
726
|
+
configWithForwarding,
|
|
727
|
+
additionalForwarding,
|
|
728
|
+
serve: currentServe,
|
|
729
|
+
autoReconnect
|
|
730
|
+
});
|
|
731
|
+
if (existingTunnel.createdAt) {
|
|
732
|
+
newTunnel.createdAt = existingTunnel.createdAt;
|
|
733
|
+
}
|
|
734
|
+
await this.startTunnel(newTunnel.tunnelid);
|
|
735
|
+
} catch (error) {
|
|
736
|
+
logger.error("Failed to restart tunnel", {
|
|
737
|
+
tunnelid,
|
|
738
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
739
|
+
});
|
|
740
|
+
throw new Error(`Failed to restart tunnel: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1048
741
|
}
|
|
1049
742
|
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
743
|
+
/**
|
|
744
|
+
* Updates the configuration of an existing tunnel.
|
|
745
|
+
*
|
|
746
|
+
* This method handles the process of updating a tunnel's configuration while preserving
|
|
747
|
+
* its state. If the tunnel is running, it will be stopped, updated, and restarted.
|
|
748
|
+
* In case of failure, it attempts to restore the original configuration.
|
|
749
|
+
*
|
|
750
|
+
* @param newConfig - The new configuration to apply, including configid and optional additional forwarding
|
|
751
|
+
*
|
|
752
|
+
* @returns Promise resolving to the updated ManagedTunnel
|
|
753
|
+
* @throws Error if the tunnel is not found or if the update process fails
|
|
754
|
+
*/
|
|
755
|
+
async updateConfig(newConfig) {
|
|
756
|
+
const { configid, tunnelName: newTunnelName, additionalForwarding } = newConfig;
|
|
757
|
+
if (!configid || configid.trim().length === 0) {
|
|
758
|
+
throw new Error(`Invalid configid: "${configid}"`);
|
|
759
|
+
}
|
|
760
|
+
const existingTunnel = this.tunnelsByConfigId.get(configid);
|
|
761
|
+
if (!existingTunnel) {
|
|
762
|
+
throw new Error(`Tunnel with config id "${configid}" not found`);
|
|
763
|
+
}
|
|
764
|
+
const isStopped = existingTunnel.isStopped;
|
|
765
|
+
const currentTunnelConfig = existingTunnel.tunnelConfig;
|
|
766
|
+
const currentConfigWithForwarding = existingTunnel.configWithForwarding;
|
|
767
|
+
const currentTunnelId = existingTunnel.tunnelid;
|
|
768
|
+
const currentTunnelConfigId = existingTunnel.configid;
|
|
769
|
+
const currentAdditionalForwarding = existingTunnel.additionalForwarding;
|
|
770
|
+
const currentTunnelName = existingTunnel.tunnelName;
|
|
771
|
+
const currentServe = existingTunnel.serve;
|
|
772
|
+
const currentAutoReconnect = existingTunnel.autoReconnect || false;
|
|
773
|
+
try {
|
|
774
|
+
if (!isStopped) {
|
|
775
|
+
existingTunnel.instance.stop();
|
|
776
|
+
}
|
|
777
|
+
this.tunnelsByTunnelId.delete(currentTunnelId);
|
|
778
|
+
this.tunnelsByConfigId.delete(currentTunnelConfigId);
|
|
779
|
+
const mergedBaseConfig = {
|
|
780
|
+
...newConfig,
|
|
781
|
+
configid,
|
|
782
|
+
tunnelName: newTunnelName !== void 0 ? newTunnelName : currentTunnelName,
|
|
783
|
+
serve: newConfig.serve !== void 0 ? newConfig.serve : currentServe
|
|
784
|
+
};
|
|
785
|
+
const newConfigWithForwarding = this.buildPinggyConfig(
|
|
786
|
+
mergedBaseConfig,
|
|
787
|
+
additionalForwarding !== void 0 ? additionalForwarding : currentAdditionalForwarding
|
|
788
|
+
);
|
|
789
|
+
const newTunnel = await this._createTunnelWithProcessedConfig({
|
|
790
|
+
configid,
|
|
791
|
+
tunnelid: currentTunnelId,
|
|
792
|
+
tunnelName: newTunnelName !== void 0 ? newTunnelName : currentTunnelName,
|
|
793
|
+
originalConfig: mergedBaseConfig,
|
|
794
|
+
configWithForwarding: newConfigWithForwarding,
|
|
795
|
+
additionalForwarding: additionalForwarding !== void 0 ? additionalForwarding : currentAdditionalForwarding,
|
|
796
|
+
serve: newConfig.serve !== void 0 ? newConfig.serve : currentServe,
|
|
797
|
+
autoReconnect: currentAutoReconnect
|
|
798
|
+
});
|
|
799
|
+
if (!isStopped) {
|
|
800
|
+
await this.startTunnel(newTunnel.tunnelid);
|
|
801
|
+
}
|
|
802
|
+
logger.info("Tunnel configuration updated", {
|
|
803
|
+
tunnelId: newTunnel.tunnelid,
|
|
804
|
+
configId: newTunnel.configid,
|
|
805
|
+
isStopped
|
|
806
|
+
});
|
|
807
|
+
return newTunnel;
|
|
808
|
+
} catch (error) {
|
|
809
|
+
logger.error("Error updating tunnel configuration", {
|
|
810
|
+
configId: configid,
|
|
811
|
+
error: error instanceof Error ? error.message : String(error)
|
|
812
|
+
});
|
|
813
|
+
try {
|
|
814
|
+
const originalTunnel = await this._createTunnelWithProcessedConfig({
|
|
815
|
+
configid: currentTunnelConfigId,
|
|
816
|
+
tunnelid: currentTunnelId,
|
|
817
|
+
tunnelName: currentTunnelName,
|
|
818
|
+
originalConfig: currentTunnelConfig,
|
|
819
|
+
configWithForwarding: currentConfigWithForwarding,
|
|
820
|
+
additionalForwarding: currentAdditionalForwarding,
|
|
821
|
+
serve: currentServe,
|
|
822
|
+
autoReconnect: currentAutoReconnect
|
|
823
|
+
});
|
|
824
|
+
if (!isStopped) {
|
|
825
|
+
await this.startTunnel(originalTunnel.tunnelid);
|
|
826
|
+
}
|
|
827
|
+
logger.warn("Restored original tunnel configuration after update failure", {
|
|
828
|
+
currentTunnelId,
|
|
829
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
830
|
+
});
|
|
831
|
+
} catch (restoreError) {
|
|
832
|
+
logger.error("Failed to restore original tunnel configuration", {
|
|
833
|
+
currentTunnelId,
|
|
834
|
+
error: restoreError instanceof Error ? restoreError.message : "Unknown error"
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
throw error;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Retrieve the ManagedTunnel object by either configId or tunnelId.
|
|
842
|
+
* Throws an error if neither id is provided or the tunnel is not found.
|
|
843
|
+
*/
|
|
844
|
+
getManagedTunnel(configId, tunnelId) {
|
|
845
|
+
if (configId) {
|
|
846
|
+
const managed = this.tunnelsByConfigId.get(configId);
|
|
847
|
+
if (!managed) throw new Error(`Tunnel "${configId}" not found`);
|
|
848
|
+
return managed;
|
|
849
|
+
}
|
|
850
|
+
if (tunnelId) {
|
|
851
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
852
|
+
if (!managed) throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
853
|
+
return managed;
|
|
854
|
+
}
|
|
855
|
+
throw new Error(`Either configId or tunnelId must be provided`);
|
|
856
|
+
}
|
|
857
|
+
async getTunnelGreetMessage(tunnelId) {
|
|
858
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
859
|
+
if (!managed) {
|
|
860
|
+
logger.error(`Tunnel "${tunnelId}" not found when fetching greet message`);
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
1057
863
|
try {
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
864
|
+
if (managed.isStopped) {
|
|
865
|
+
return null;
|
|
866
|
+
}
|
|
867
|
+
const messages = await managed.instance.getGreetMessage();
|
|
868
|
+
if (Array.isArray(messages)) {
|
|
869
|
+
return messages.join(" ");
|
|
870
|
+
}
|
|
871
|
+
return messages ?? null;
|
|
1062
872
|
} catch (e) {
|
|
1063
|
-
logger.
|
|
873
|
+
logger.error(
|
|
874
|
+
`Error fetching greet message for tunnel "${tunnelId}": ${e instanceof Error ? e.message : String(e)}`
|
|
875
|
+
);
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
getTunnelStats(tunnelId) {
|
|
880
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
881
|
+
if (!managed) {
|
|
882
|
+
return null;
|
|
883
|
+
}
|
|
884
|
+
const stats = this.tunnelStats.get(tunnelId);
|
|
885
|
+
return stats || null;
|
|
886
|
+
}
|
|
887
|
+
getLatestTunnelStats(tunnelId) {
|
|
888
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
889
|
+
if (!managed) {
|
|
890
|
+
return null;
|
|
891
|
+
}
|
|
892
|
+
const stats = this.tunnelStats.get(tunnelId);
|
|
893
|
+
if (stats && stats.length > 0) {
|
|
894
|
+
return stats[stats.length - 1];
|
|
895
|
+
}
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Registers a listener function to receive tunnel statistics updates.
|
|
900
|
+
* The listener will be called whenever any tunnel's stats are updated.
|
|
901
|
+
*
|
|
902
|
+
* @param tunnelId - The tunnel ID to listen to stats for
|
|
903
|
+
* @param listener - Function that receives tunnelId and stats when updates occur
|
|
904
|
+
* @returns A unique listener ID that can be used to deregister the listener and tunnelId
|
|
905
|
+
*
|
|
906
|
+
* @throws {Error} When the specified tunnelId does not exist
|
|
907
|
+
*/
|
|
908
|
+
async registerStatsListener(tunnelId, listener) {
|
|
909
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
910
|
+
if (!managed) {
|
|
911
|
+
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
912
|
+
}
|
|
913
|
+
if (!this.tunnelStatsListeners.has(tunnelId)) {
|
|
914
|
+
this.tunnelStatsListeners.set(tunnelId, /* @__PURE__ */ new Map());
|
|
915
|
+
}
|
|
916
|
+
const listenerId = getRandomId();
|
|
917
|
+
const tunnelListeners = this.tunnelStatsListeners.get(tunnelId);
|
|
918
|
+
tunnelListeners.set(listenerId, listener);
|
|
919
|
+
logger.info("Stats listener registered for tunnel", { tunnelId, listenerId });
|
|
920
|
+
return [listenerId, tunnelId];
|
|
921
|
+
}
|
|
922
|
+
async registerErrorListener(tunnelId, listener) {
|
|
923
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
924
|
+
if (!managed) {
|
|
925
|
+
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
926
|
+
}
|
|
927
|
+
if (!this.tunnelErrorListeners.has(tunnelId)) {
|
|
928
|
+
this.tunnelErrorListeners.set(tunnelId, /* @__PURE__ */ new Map());
|
|
929
|
+
}
|
|
930
|
+
const listenerId = getRandomId();
|
|
931
|
+
const tunnelErrorListeners = this.tunnelErrorListeners.get(tunnelId);
|
|
932
|
+
tunnelErrorListeners.set(listenerId, listener);
|
|
933
|
+
logger.info("Error listener registered for tunnel", { tunnelId, listenerId });
|
|
934
|
+
return listenerId;
|
|
935
|
+
}
|
|
936
|
+
async registerDisconnectListener(tunnelId, listener) {
|
|
937
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
938
|
+
if (!managed) {
|
|
939
|
+
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
940
|
+
}
|
|
941
|
+
if (!this.tunnelDisconnectListeners.has(tunnelId)) {
|
|
942
|
+
this.tunnelDisconnectListeners.set(tunnelId, /* @__PURE__ */ new Map());
|
|
943
|
+
}
|
|
944
|
+
const listenerId = getRandomId();
|
|
945
|
+
const tunnelDisconnectListeners = this.tunnelDisconnectListeners.get(tunnelId);
|
|
946
|
+
tunnelDisconnectListeners.set(listenerId, listener);
|
|
947
|
+
logger.info("Disconnect listener registered for tunnel", { tunnelId, listenerId });
|
|
948
|
+
return listenerId;
|
|
949
|
+
}
|
|
950
|
+
async registerWorkerErrorListner(tunnelId, listener) {
|
|
951
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
952
|
+
if (!managed) {
|
|
953
|
+
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
954
|
+
}
|
|
955
|
+
if (!this.tunnelWorkerErrorListeners.has(tunnelId)) {
|
|
956
|
+
this.tunnelWorkerErrorListeners.set(tunnelId, /* @__PURE__ */ new Map());
|
|
957
|
+
}
|
|
958
|
+
const listenerId = getRandomId();
|
|
959
|
+
const tunnelWorkerErrorListner = this.tunnelWorkerErrorListeners.get(tunnelId);
|
|
960
|
+
tunnelWorkerErrorListner?.set(listenerId, listener);
|
|
961
|
+
logger.info("TunnelWorker error listener registered for tunnel", { tunnelId, listenerId });
|
|
962
|
+
}
|
|
963
|
+
async registerStartListener(tunnelId, listener) {
|
|
964
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
965
|
+
if (!managed) {
|
|
966
|
+
throw new Error(`Tunnel "${tunnelId}" not found`);
|
|
967
|
+
}
|
|
968
|
+
if (!this.tunnelStartListeners.has(tunnelId)) {
|
|
969
|
+
this.tunnelStartListeners.set(tunnelId, /* @__PURE__ */ new Map());
|
|
970
|
+
}
|
|
971
|
+
const listenerId = getRandomId();
|
|
972
|
+
const listeners = this.tunnelStartListeners.get(tunnelId);
|
|
973
|
+
listeners.set(listenerId, listener);
|
|
974
|
+
logger.info("Start listener registered for tunnel", { tunnelId, listenerId });
|
|
975
|
+
return listenerId;
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Removes a previously registered stats listener.
|
|
979
|
+
*
|
|
980
|
+
* @param tunnelId - The tunnel ID the listener was registered for
|
|
981
|
+
* @param listenerId - The unique ID returned when the listener was registered
|
|
982
|
+
*/
|
|
983
|
+
deregisterStatsListener(tunnelId, listenerId) {
|
|
984
|
+
const tunnelListeners = this.tunnelStatsListeners.get(tunnelId);
|
|
985
|
+
if (!tunnelListeners) {
|
|
986
|
+
logger.warn("No listeners found for tunnel", { tunnelId });
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
const removed = tunnelListeners.delete(listenerId);
|
|
990
|
+
if (removed) {
|
|
991
|
+
logger.info("Stats listener deregistered", { tunnelId, listenerId });
|
|
992
|
+
if (tunnelListeners.size === 0) {
|
|
993
|
+
this.tunnelStatsListeners.delete(tunnelId);
|
|
994
|
+
}
|
|
995
|
+
} else {
|
|
996
|
+
logger.warn("Attempted to deregister non-existent stats listener", { tunnelId, listenerId });
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
deregisterErrorListener(tunnelId, listenerId) {
|
|
1000
|
+
const listeners = this.tunnelErrorListeners.get(tunnelId);
|
|
1001
|
+
if (!listeners) {
|
|
1002
|
+
logger.warn("No error listeners found for tunnel", { tunnelId });
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
const removed = listeners.delete(listenerId);
|
|
1006
|
+
if (removed) {
|
|
1007
|
+
logger.info("Error listener deregistered", { tunnelId, listenerId });
|
|
1008
|
+
if (listeners.size === 0) {
|
|
1009
|
+
this.tunnelErrorListeners.delete(tunnelId);
|
|
1010
|
+
}
|
|
1011
|
+
} else {
|
|
1012
|
+
logger.warn("Attempted to deregister non-existent error listener", { tunnelId, listenerId });
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
deregisterDisconnectListener(tunnelId, listenerId) {
|
|
1016
|
+
const listeners = this.tunnelDisconnectListeners.get(tunnelId);
|
|
1017
|
+
if (!listeners) {
|
|
1018
|
+
logger.warn("No disconnect listeners found for tunnel", { tunnelId });
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
const removed = listeners.delete(listenerId);
|
|
1022
|
+
if (removed) {
|
|
1023
|
+
logger.info("Disconnect listener deregistered", { tunnelId, listenerId });
|
|
1024
|
+
if (listeners.size === 0) {
|
|
1025
|
+
this.tunnelDisconnectListeners.delete(tunnelId);
|
|
1026
|
+
}
|
|
1027
|
+
} else {
|
|
1028
|
+
logger.warn("Attempted to deregister non-existent disconnect listener", { tunnelId, listenerId });
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
async getLocalserverTlsInfo(tunnelId) {
|
|
1032
|
+
const managed = this.tunnelsByTunnelId.get(tunnelId);
|
|
1033
|
+
if (!managed) {
|
|
1034
|
+
logger.error(`Tunnel "${tunnelId}" not found when fetching local server TLS info`);
|
|
1035
|
+
return false;
|
|
1064
1036
|
}
|
|
1065
|
-
};
|
|
1066
|
-
managed.instance.setTunnelErrorCallback(callback);
|
|
1067
|
-
logger.debug("Error callback set up for tunnel", { tunnelId });
|
|
1068
|
-
} catch (error) {
|
|
1069
|
-
logger.warn("Failed to set up error callback", { tunnelId, error });
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
setupDisconnectCallback(tunnelId, managed) {
|
|
1073
|
-
try {
|
|
1074
|
-
const callback = ({ error, messages }) => {
|
|
1075
1037
|
try {
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
if (managedTunnel) {
|
|
1079
|
-
managedTunnel.isStopped = true;
|
|
1080
|
-
managedTunnel.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1038
|
+
if (managed.isStopped) {
|
|
1039
|
+
return false;
|
|
1081
1040
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
try {
|
|
1086
|
-
await this.restartTunnel(tunnelId);
|
|
1087
|
-
logger.info("Tunnel auto-reconnected successfully", { tunnelId });
|
|
1088
|
-
} catch (e) {
|
|
1089
|
-
logger.error("Failed to auto-reconnect tunnel", { tunnelId, e });
|
|
1090
|
-
}
|
|
1091
|
-
}, 1e4);
|
|
1041
|
+
const tlsInfo = await managed.instance.getLocalServerTls();
|
|
1042
|
+
if (tlsInfo) {
|
|
1043
|
+
return tlsInfo;
|
|
1092
1044
|
}
|
|
1093
|
-
|
|
1045
|
+
return false;
|
|
1046
|
+
} catch (e) {
|
|
1047
|
+
logger.error(`Error fetching TLS info for tunnel "${tunnelId}": ${e instanceof Error ? e.message : e}`);
|
|
1048
|
+
return false;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Sets up the stats callback for a tunnel during creation.
|
|
1053
|
+
* This callback will update stored stats and notify all registered listeners.
|
|
1054
|
+
*/
|
|
1055
|
+
setupStatsCallback(tunnelId, managed) {
|
|
1056
|
+
try {
|
|
1057
|
+
const callback = (usage) => {
|
|
1058
|
+
this.updateStats(tunnelId, usage);
|
|
1059
|
+
};
|
|
1060
|
+
managed.instance.setUsageUpdateCallback(callback);
|
|
1061
|
+
logger.debug("Stats callback set up for tunnel", { tunnelId });
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
logger.warn("Failed to set up stats callback", { tunnelId, error });
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
notifyErrorListeners(tunnelId, errorMsg, isFatal) {
|
|
1067
|
+
try {
|
|
1068
|
+
const listeners = this.tunnelErrorListeners.get(tunnelId);
|
|
1094
1069
|
if (!listeners) return;
|
|
1095
1070
|
for (const [id, listener] of listeners) {
|
|
1096
1071
|
try {
|
|
1097
|
-
listener(tunnelId,
|
|
1072
|
+
listener(tunnelId, errorMsg, isFatal);
|
|
1098
1073
|
} catch (err) {
|
|
1099
|
-
logger.debug("Error in
|
|
1074
|
+
logger.debug("Error in error-listener callback", { listenerId: id, tunnelId, err });
|
|
1100
1075
|
}
|
|
1101
1076
|
}
|
|
1102
|
-
} catch (
|
|
1103
|
-
logger.
|
|
1077
|
+
} catch (err) {
|
|
1078
|
+
logger.debug("Failed to notify error listeners", { tunnelId, err });
|
|
1104
1079
|
}
|
|
1105
|
-
}
|
|
1106
|
-
managed
|
|
1107
|
-
logger.debug("Disconnect callback set up for tunnel", { tunnelId });
|
|
1108
|
-
} catch (error) {
|
|
1109
|
-
logger.warn("Failed to set up disconnect callback", { tunnelId, error });
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
setUpTunnelWorkerErrorCallback(tunnelId, managed) {
|
|
1113
|
-
try {
|
|
1114
|
-
const callback = (error) => {
|
|
1080
|
+
}
|
|
1081
|
+
setupErrorCallback(tunnelId, managed) {
|
|
1115
1082
|
try {
|
|
1116
|
-
|
|
1117
|
-
const listeners = this.tunnelWorkerErrorListeners.get(tunnelId);
|
|
1118
|
-
if (!listeners) return;
|
|
1119
|
-
for (const [id, listener] of listeners) {
|
|
1083
|
+
const callback = ({ errorNo, error, recoverable }) => {
|
|
1120
1084
|
try {
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
logger.debug("
|
|
1085
|
+
const msg = typeof error === "string" ? error : String(error);
|
|
1086
|
+
const isFatal = true;
|
|
1087
|
+
logger.debug("Tunnel reported error", { tunnelId, errorNo, errorMsg: msg, recoverable });
|
|
1088
|
+
this.notifyErrorListeners(tunnelId, msg, isFatal);
|
|
1089
|
+
} catch (e) {
|
|
1090
|
+
logger.warn("Error handling tunnel error callback", { tunnelId, e });
|
|
1124
1091
|
}
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
logger.
|
|
1092
|
+
};
|
|
1093
|
+
managed.instance.setTunnelErrorCallback(callback);
|
|
1094
|
+
logger.debug("Error callback set up for tunnel", { tunnelId });
|
|
1095
|
+
} catch (error) {
|
|
1096
|
+
logger.warn("Failed to set up error callback", { tunnelId, error });
|
|
1128
1097
|
}
|
|
1129
|
-
}
|
|
1130
|
-
managed
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1098
|
+
}
|
|
1099
|
+
setupDisconnectCallback(tunnelId, managed) {
|
|
1100
|
+
try {
|
|
1101
|
+
const callback = ({ error, messages }) => {
|
|
1102
|
+
try {
|
|
1103
|
+
logger.debug("Tunnel disconnected", { tunnelId, error, messages });
|
|
1104
|
+
const managedTunnel = this.tunnelsByTunnelId.get(tunnelId);
|
|
1105
|
+
if (managedTunnel) {
|
|
1106
|
+
managedTunnel.isStopped = true;
|
|
1107
|
+
managedTunnel.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1108
|
+
}
|
|
1109
|
+
if (managedTunnel && managedTunnel.autoReconnect) {
|
|
1110
|
+
logger.info("Auto-reconnecting tunnel", { tunnelId });
|
|
1111
|
+
setTimeout(async () => {
|
|
1112
|
+
try {
|
|
1113
|
+
await this.restartTunnel(tunnelId);
|
|
1114
|
+
logger.info("Tunnel auto-reconnected successfully", { tunnelId });
|
|
1115
|
+
} catch (e) {
|
|
1116
|
+
logger.error("Failed to auto-reconnect tunnel", { tunnelId, e });
|
|
1117
|
+
}
|
|
1118
|
+
}, 1e4);
|
|
1119
|
+
}
|
|
1120
|
+
const listeners = this.tunnelDisconnectListeners.get(tunnelId);
|
|
1121
|
+
if (!listeners) return;
|
|
1122
|
+
for (const [id, listener] of listeners) {
|
|
1123
|
+
try {
|
|
1124
|
+
listener(tunnelId, error, messages);
|
|
1125
|
+
} catch (err) {
|
|
1126
|
+
logger.debug("Error in disconnect-listener callback", { listenerId: id, tunnelId, err });
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
} catch (e) {
|
|
1130
|
+
logger.warn("Error handling tunnel disconnect callback", { tunnelId, e });
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
managed.instance.setTunnelDisconnectedCallback(callback);
|
|
1134
|
+
logger.debug("Disconnect callback set up for tunnel", { tunnelId });
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
logger.warn("Failed to set up disconnect callback", { tunnelId, error });
|
|
1153
1137
|
}
|
|
1154
1138
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
numTotalReqBytes: reqBytes,
|
|
1178
|
-
numTotalResBytes: resBytes,
|
|
1179
|
-
numTotalTxBytes: txBytes
|
|
1180
|
-
};
|
|
1181
|
-
}
|
|
1182
|
-
parseNumber(value) {
|
|
1183
|
-
const parsed = typeof value === "number" ? value : parseInt(String(value), 10);
|
|
1184
|
-
return isNaN(parsed) ? 0 : parsed;
|
|
1185
|
-
}
|
|
1186
|
-
startStaticFileServer(managed) {
|
|
1187
|
-
try {
|
|
1188
|
-
const __filename3 = (0, import_node_url.fileURLToPath)(importMetaUrl);
|
|
1189
|
-
const __dirname2 = import_node_path.default.dirname(__filename3);
|
|
1190
|
-
const fileServerWorkerPath = import_node_path.default.join(__dirname2, "workers", "file_serve_worker.cjs");
|
|
1191
|
-
const staticServerWorker = new import_node_worker_threads.Worker(fileServerWorkerPath, {
|
|
1192
|
-
workerData: {
|
|
1193
|
-
dir: managed.serve,
|
|
1194
|
-
port: managed.tunnelConfig?.forwarding
|
|
1139
|
+
setUpTunnelWorkerErrorCallback(tunnelId, managed) {
|
|
1140
|
+
try {
|
|
1141
|
+
const callback = (error) => {
|
|
1142
|
+
try {
|
|
1143
|
+
logger.debug("Error in Tunnel Worker", { tunnelId, errorMessage: error.message });
|
|
1144
|
+
const listeners = this.tunnelWorkerErrorListeners.get(tunnelId);
|
|
1145
|
+
if (!listeners) return;
|
|
1146
|
+
for (const [id, listener] of listeners) {
|
|
1147
|
+
try {
|
|
1148
|
+
listener(tunnelId, error);
|
|
1149
|
+
} catch (err) {
|
|
1150
|
+
logger.debug("Error in worker-error-listener callback", { listenerId: id, tunnelId, err });
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
} catch (e) {
|
|
1154
|
+
logger.warn("Error handling tunnel worker error callback", { tunnelId, e });
|
|
1155
|
+
}
|
|
1156
|
+
};
|
|
1157
|
+
managed.instance.setWorkerErrorCallback(callback);
|
|
1158
|
+
logger.debug("Disconnect callback set up for tunnel", { tunnelId });
|
|
1159
|
+
} catch (error) {
|
|
1160
|
+
logger.warn("Failed to setup tunnel worker error callback");
|
|
1195
1161
|
}
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Updates the stored stats for a tunnel and notifies all registered listeners.
|
|
1165
|
+
*/
|
|
1166
|
+
updateStats(tunnelId, rawUsage) {
|
|
1167
|
+
try {
|
|
1168
|
+
const normalizedStats = this.normalizeStats(rawUsage);
|
|
1169
|
+
const existingStats = this.tunnelStats.get(tunnelId) || [];
|
|
1170
|
+
const updatedStats = [...existingStats, normalizedStats];
|
|
1171
|
+
this.tunnelStats.set(tunnelId, updatedStats);
|
|
1172
|
+
const tunnelListeners = this.tunnelStatsListeners.get(tunnelId);
|
|
1173
|
+
if (tunnelListeners) {
|
|
1174
|
+
for (const [listenerId, listener] of tunnelListeners) {
|
|
1175
|
+
try {
|
|
1176
|
+
listener(tunnelId, normalizedStats);
|
|
1177
|
+
} catch (error) {
|
|
1178
|
+
logger.warn("Error in stats listener callback", { listenerId, tunnelId, error });
|
|
1179
|
+
}
|
|
1206
1180
|
}
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
});
|
|
1215
|
-
break;
|
|
1181
|
+
}
|
|
1182
|
+
logger.debug("Stats updated and listeners notified", {
|
|
1183
|
+
tunnelId,
|
|
1184
|
+
listenersCount: tunnelListeners?.size || 0
|
|
1185
|
+
});
|
|
1186
|
+
} catch (error) {
|
|
1187
|
+
logger.warn("Error updating stats", { tunnelId, error });
|
|
1216
1188
|
}
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Normalizes raw usage data from the SDK into a consistent TunnelStats format.
|
|
1192
|
+
*/
|
|
1193
|
+
normalizeStats(rawStats) {
|
|
1194
|
+
const elapsed = this.parseNumber(rawStats.elapsedTime ?? 0);
|
|
1195
|
+
const liveConns = this.parseNumber(rawStats.numLiveConnections ?? 0);
|
|
1196
|
+
const totalConns = this.parseNumber(rawStats.numTotalConnections ?? 0);
|
|
1197
|
+
const reqBytes = this.parseNumber(rawStats.numTotalReqBytes ?? 0);
|
|
1198
|
+
const resBytes = this.parseNumber(rawStats.numTotalResBytes ?? 0);
|
|
1199
|
+
const txBytes = this.parseNumber(rawStats.numTotalTxBytes ?? 0);
|
|
1200
|
+
return {
|
|
1201
|
+
elapsedTime: elapsed,
|
|
1202
|
+
numLiveConnections: liveConns,
|
|
1203
|
+
numTotalConnections: totalConns,
|
|
1204
|
+
numTotalReqBytes: reqBytes,
|
|
1205
|
+
numTotalResBytes: resBytes,
|
|
1206
|
+
numTotalTxBytes: txBytes
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
parseNumber(value) {
|
|
1210
|
+
const parsed = typeof value === "number" ? value : parseInt(String(value), 10);
|
|
1211
|
+
return isNaN(parsed) ? 0 : parsed;
|
|
1212
|
+
}
|
|
1213
|
+
startStaticFileServer(managed) {
|
|
1214
|
+
try {
|
|
1215
|
+
const __filename3 = (0, import_node_url.fileURLToPath)(importMetaUrl);
|
|
1216
|
+
const __dirname2 = import_node_path.default.dirname(__filename3);
|
|
1217
|
+
const fileServerWorkerPath = import_node_path.default.join(__dirname2, "workers", "file_serve_worker.cjs");
|
|
1218
|
+
const staticServerWorker = new import_node_worker_threads.Worker(fileServerWorkerPath, {
|
|
1219
|
+
workerData: {
|
|
1220
|
+
dir: managed.serve,
|
|
1221
|
+
port: managed.tunnelConfig?.forwarding
|
|
1222
|
+
}
|
|
1223
|
+
});
|
|
1224
|
+
staticServerWorker.on("message", (msg) => {
|
|
1225
|
+
switch (msg.type) {
|
|
1226
|
+
case "started":
|
|
1227
|
+
logger.info("Static file server started", { dir: managed.serve });
|
|
1228
|
+
break;
|
|
1229
|
+
case "warning":
|
|
1230
|
+
if (msg.code === "INVALID_TUNNEL_SERVE_PATH") {
|
|
1231
|
+
managed.warnings = managed.warnings ?? [];
|
|
1232
|
+
managed.warnings.push({ code: msg.code, message: msg.message });
|
|
1233
|
+
}
|
|
1234
|
+
printer_default.warn(msg.message);
|
|
1235
|
+
break;
|
|
1236
|
+
case "error":
|
|
1237
|
+
managed.warnings = managed.warnings ?? [];
|
|
1238
|
+
managed.warnings.push({
|
|
1239
|
+
code: "UNKNOWN_WARNING",
|
|
1240
|
+
message: msg.message
|
|
1241
|
+
});
|
|
1242
|
+
break;
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
managed.serveWorker = staticServerWorker;
|
|
1246
|
+
} catch (error) {
|
|
1247
|
+
logger.error("Error starting static file server", error);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
};
|
|
1222
1251
|
}
|
|
1223
|
-
};
|
|
1252
|
+
});
|
|
1224
1253
|
|
|
1225
1254
|
// src/cli/options.ts
|
|
1226
|
-
var cliOptions
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
}
|
|
1255
|
+
var cliOptions;
|
|
1256
|
+
var init_options = __esm({
|
|
1257
|
+
"src/cli/options.ts"() {
|
|
1258
|
+
"use strict";
|
|
1259
|
+
init_cjs_shims();
|
|
1260
|
+
cliOptions = {
|
|
1261
|
+
// SSH-like options
|
|
1262
|
+
R: { type: "string", multiple: true, description: "Local port. Eg. -R0:localhost:3000 will forward tunnel connections to local port 3000." },
|
|
1263
|
+
L: { type: "string", multiple: true, description: "Web Debugger address. Eg. -L4300:localhost:4300 will start web debugger on port 4300." },
|
|
1264
|
+
o: { type: "string", multiple: true, description: "Options", hidden: true },
|
|
1265
|
+
"server-port": { type: "string", short: "p", description: "Pinggy server port. Default: 443" },
|
|
1266
|
+
v4: { type: "boolean", short: "4", description: "IPv4 only", hidden: true },
|
|
1267
|
+
v6: { type: "boolean", short: "6", description: "IPv6 only", hidden: true },
|
|
1268
|
+
// These options appear in the ssh command, but we ignore it in CLI
|
|
1269
|
+
t: { type: "boolean", description: "hidden", hidden: true },
|
|
1270
|
+
T: { type: "boolean", description: "hidden", hidden: true },
|
|
1271
|
+
n: { type: "boolean", description: "hidden", hidden: true },
|
|
1272
|
+
N: { type: "boolean", description: "hidden", hidden: true },
|
|
1273
|
+
// Better options
|
|
1274
|
+
type: { type: "string", description: "Type of the connection. Eg. --type tcp" },
|
|
1275
|
+
localport: { type: "string", short: "l", description: "Takes input as [protocol:][host:]port. Eg. --localport https://localhost:8000 OR -l 3000" },
|
|
1276
|
+
debugger: { type: "string", short: "d", description: "Port for web debugger. Eg. --debugger 4300 OR -d 4300" },
|
|
1277
|
+
token: { type: "string", description: "Token for authentication. Eg. --token TOKEN_VALUE" },
|
|
1278
|
+
// Logging options (CLI overrides env)
|
|
1279
|
+
loglevel: { type: "string", description: "Logging level: ERROR, INFO, DEBUG. Overrides PINGGY_LOG_LEVEL environment variable" },
|
|
1280
|
+
logfile: { type: "string", description: "Path to log file. Overrides PINGGY_LOG_FILE environment variable" },
|
|
1281
|
+
v: { type: "boolean", description: "Print logs to stdout for Cli. Overrides PINGGY_LOG_STDOUT environment variable" },
|
|
1282
|
+
vv: { type: "boolean", description: "Enable detailed logging for the Node.js SDK and Libpinggy, including both info and debug level logs." },
|
|
1283
|
+
vvv: { type: "boolean", description: "Enable all logs from Cli, SDK and internal components." },
|
|
1284
|
+
autoreconnect: { type: "boolean", short: "a", description: "Automatically reconnect tunnel on failure." },
|
|
1285
|
+
// Save and load config
|
|
1286
|
+
saveconf: { type: "string", description: "Create the configuration file based on the options provided here" },
|
|
1287
|
+
conf: { type: "string", description: "Use the configuration file as base. Other options will be used to override this file" },
|
|
1288
|
+
// File server
|
|
1289
|
+
serve: { type: "string", description: "Start a webserver to serve files from the specified path. Eg --serve /path/to/files" },
|
|
1290
|
+
// Remote Control
|
|
1291
|
+
"remote-management": { type: "string", description: "Enable remote management of tunnels with token. Eg. --remote-management API_KEY" },
|
|
1292
|
+
manage: { type: "string", description: "Provide a server address to manage tunnels. Eg --manage dashboard.pinggy.io" },
|
|
1293
|
+
notui: { type: "boolean", description: "Disable TUI in remote management mode" },
|
|
1294
|
+
// Misc
|
|
1295
|
+
version: { type: "boolean", description: "Print version" },
|
|
1296
|
+
// Help
|
|
1297
|
+
help: { type: "boolean", short: "h", description: "Show this help message" }
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
});
|
|
1265
1301
|
|
|
1266
1302
|
// src/cli/help.ts
|
|
1267
1303
|
function printHelpMessage() {
|
|
@@ -1299,29 +1335,42 @@ function printHelpMessage() {
|
|
|
1299
1335
|
console.log(" pinggy -R0:localhost:8080 -L4300:localhost:4300 # HTTP tunnel with debugger");
|
|
1300
1336
|
console.log(" pinggy tcp@ap.example.com -R0:localhost:22 # TCP tunnel to region\n");
|
|
1301
1337
|
}
|
|
1338
|
+
var init_help = __esm({
|
|
1339
|
+
"src/cli/help.ts"() {
|
|
1340
|
+
"use strict";
|
|
1341
|
+
init_cjs_shims();
|
|
1342
|
+
init_options();
|
|
1343
|
+
}
|
|
1344
|
+
});
|
|
1302
1345
|
|
|
1303
1346
|
// src/cli/defaults.ts
|
|
1304
|
-
var defaultOptions
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1347
|
+
var defaultOptions;
|
|
1348
|
+
var init_defaults = __esm({
|
|
1349
|
+
"src/cli/defaults.ts"() {
|
|
1350
|
+
"use strict";
|
|
1351
|
+
init_cjs_shims();
|
|
1352
|
+
defaultOptions = {
|
|
1353
|
+
token: void 0,
|
|
1354
|
+
// No default token
|
|
1355
|
+
serverAddress: "a.pinggy.io",
|
|
1356
|
+
forwarding: "localhost:8000",
|
|
1357
|
+
webDebugger: "",
|
|
1358
|
+
ipWhitelist: [],
|
|
1359
|
+
basicAuth: [],
|
|
1360
|
+
bearerTokenAuth: [],
|
|
1361
|
+
headerModification: [],
|
|
1362
|
+
force: false,
|
|
1363
|
+
xForwardedFor: false,
|
|
1364
|
+
httpsOnly: false,
|
|
1365
|
+
originalRequestUrl: false,
|
|
1366
|
+
allowPreflight: false,
|
|
1367
|
+
reverseProxy: false,
|
|
1368
|
+
autoReconnect: false
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
});
|
|
1322
1372
|
|
|
1323
1373
|
// src/cli/extendedOptions.ts
|
|
1324
|
-
var import_net = require("net");
|
|
1325
1374
|
function parseExtendedOptions(options, config) {
|
|
1326
1375
|
if (!options) return;
|
|
1327
1376
|
for (const opt of options) {
|
|
@@ -1445,12 +1494,18 @@ function isValidIpV6Cidr(input) {
|
|
|
1445
1494
|
}
|
|
1446
1495
|
return false;
|
|
1447
1496
|
}
|
|
1497
|
+
var import_net;
|
|
1498
|
+
var init_extendedOptions = __esm({
|
|
1499
|
+
"src/cli/extendedOptions.ts"() {
|
|
1500
|
+
"use strict";
|
|
1501
|
+
init_cjs_shims();
|
|
1502
|
+
import_net = require("net");
|
|
1503
|
+
init_logger();
|
|
1504
|
+
init_printer();
|
|
1505
|
+
}
|
|
1506
|
+
});
|
|
1448
1507
|
|
|
1449
1508
|
// src/cli/buildConfig.ts
|
|
1450
|
-
var import_pinggy3 = require("@pinggy/pinggy");
|
|
1451
|
-
var import_fs2 = __toESM(require("fs"), 1);
|
|
1452
|
-
var import_path2 = __toESM(require("path"), 1);
|
|
1453
|
-
var domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
|
|
1454
1509
|
function parseUserAndDomain(str) {
|
|
1455
1510
|
let token;
|
|
1456
1511
|
let type;
|
|
@@ -1580,7 +1635,6 @@ function ipv6SafeSplitColon(s) {
|
|
|
1580
1635
|
result.push(buf);
|
|
1581
1636
|
return result;
|
|
1582
1637
|
}
|
|
1583
|
-
var VALID_PROTOCOLS = ["http", "tcp", "udp", "tls"];
|
|
1584
1638
|
function parseDefaultForwarding(forwarding) {
|
|
1585
1639
|
const parts = ipv6SafeSplitColon(forwarding);
|
|
1586
1640
|
if (parts.length === 3) {
|
|
@@ -1600,77 +1654,63 @@ function parseDefaultForwarding(forwarding) {
|
|
|
1600
1654
|
}
|
|
1601
1655
|
function parseAdditionalForwarding(forwarding) {
|
|
1602
1656
|
const toPort = (v) => {
|
|
1657
|
+
if (!v) return null;
|
|
1603
1658
|
const n = parseInt(v, 10);
|
|
1604
1659
|
return Number.isNaN(n) ? null : n;
|
|
1605
1660
|
};
|
|
1606
|
-
const
|
|
1661
|
+
const parsed = ipv6SafeSplitColon(forwarding);
|
|
1662
|
+
if (parsed.length !== 4) {
|
|
1663
|
+
return new Error(
|
|
1664
|
+
"forwarding must be in format: [schema//]hostname[/port][@forwardingId]:<placeholder>:<forwardingAddress>:<forwardingPort>"
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
const firstPart = parsed[0];
|
|
1668
|
+
const [hostPart] = firstPart.split("@");
|
|
1607
1669
|
let protocol = "http";
|
|
1608
1670
|
let remoteDomainRaw;
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
if (
|
|
1622
|
-
return new Error(
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1671
|
+
let remotePort = 0;
|
|
1672
|
+
if (hostPart.includes("//")) {
|
|
1673
|
+
const [schema, rest] = hostPart.split("//");
|
|
1674
|
+
if (!schema || !VALID_PROTOCOLS.includes(schema)) {
|
|
1675
|
+
return new Error(`invalid protocol: ${schema}`);
|
|
1676
|
+
}
|
|
1677
|
+
protocol = schema;
|
|
1678
|
+
const domainAndPort = rest.split("/");
|
|
1679
|
+
if (domainAndPort.length > 2) {
|
|
1680
|
+
return new Error("invalid forwarding address format");
|
|
1681
|
+
}
|
|
1682
|
+
remoteDomainRaw = domainAndPort[0];
|
|
1683
|
+
if (!remoteDomainRaw || !domainRegex.test(remoteDomainRaw)) {
|
|
1684
|
+
return new Error("invalid remote domain");
|
|
1685
|
+
}
|
|
1686
|
+
const parsedRemotePort = toPort(domainAndPort[1]);
|
|
1687
|
+
if (protocol === "http") {
|
|
1688
|
+
remotePort = 0;
|
|
1689
|
+
} else {
|
|
1690
|
+
if (parsedRemotePort === null || !isValidPort(parsedRemotePort)) {
|
|
1691
|
+
return new Error(
|
|
1692
|
+
`${protocol} forwarding requires port in format ${protocol}//domain/remotePort`
|
|
1693
|
+
);
|
|
1694
|
+
}
|
|
1695
|
+
remotePort = parsedRemotePort;
|
|
1631
1696
|
}
|
|
1632
|
-
|
|
1633
|
-
|
|
1697
|
+
} else {
|
|
1698
|
+
remoteDomainRaw = hostPart;
|
|
1699
|
+
if (!domainRegex.test(remoteDomainRaw)) {
|
|
1700
|
+
return new Error("invalid remote domain");
|
|
1634
1701
|
}
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
remoteDomain,
|
|
1638
|
-
remotePort: 0,
|
|
1639
|
-
localDomain: localDomain2,
|
|
1640
|
-
localPort: localPort2
|
|
1641
|
-
};
|
|
1642
|
-
}
|
|
1643
|
-
const domainPortMatch = remaining.match(/^([^:]+)\/(\d+):(.+)$/);
|
|
1644
|
-
if (!domainPortMatch) {
|
|
1645
|
-
return new Error(`forwarding must be in format: ${protocol}//domain/remotePort:localDomain:localPort`);
|
|
1646
|
-
}
|
|
1647
|
-
remoteDomainRaw = removeIPv6Brackets(domainPortMatch[1]);
|
|
1648
|
-
const remotePortNum = toPort(domainPortMatch[2]);
|
|
1649
|
-
const restParts = domainPortMatch[3];
|
|
1650
|
-
if (!remoteDomainRaw || !domainRegex.test(remoteDomainRaw)) {
|
|
1651
|
-
return new Error("forwarding address incorrect: invalid domain or remote port");
|
|
1652
|
-
}
|
|
1653
|
-
if (!remoteDomainRaw || remotePortNum === null || !isValidPort(remotePortNum)) {
|
|
1654
|
-
return new Error(`${protocol} forwarding: invalid domain or port in format ${protocol}//domain/remotePort`);
|
|
1655
|
-
}
|
|
1656
|
-
const parts = ipv6SafeSplitColon(restParts);
|
|
1657
|
-
if (parts.length !== 3) {
|
|
1658
|
-
return new Error(`forwarding format incorrect: expected ${protocol}//domain/remotePort:placeholder:localDomain:localPort`);
|
|
1702
|
+
protocol = "http";
|
|
1703
|
+
remotePort = 0;
|
|
1659
1704
|
}
|
|
1660
|
-
const localDomain = removeIPv6Brackets(
|
|
1661
|
-
const localPort = toPort(
|
|
1705
|
+
const localDomain = removeIPv6Brackets(parsed[2] || "localhost");
|
|
1706
|
+
const localPort = toPort(parsed[3]);
|
|
1662
1707
|
if (localPort === null || !isValidPort(localPort)) {
|
|
1663
1708
|
return new Error("forwarding address incorrect: invalid local port");
|
|
1664
1709
|
}
|
|
1665
|
-
if (protocolsRequiringDomainPort.includes(protocol)) {
|
|
1666
|
-
if (!remoteDomainRaw || !remotePortNum) {
|
|
1667
|
-
return new Error(`${protocol} forwarding requires domain and port in format: ${protocol}//domain/remotePort:localDomain:localPort`);
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
1710
|
return {
|
|
1671
1711
|
protocol,
|
|
1672
1712
|
remoteDomain: remoteDomainRaw,
|
|
1673
|
-
remotePort
|
|
1713
|
+
remotePort,
|
|
1674
1714
|
localDomain,
|
|
1675
1715
|
localPort
|
|
1676
1716
|
};
|
|
@@ -1683,19 +1723,21 @@ function parseReverseTunnelAddr(finalConfig, values) {
|
|
|
1683
1723
|
if (!Array.isArray(reverseTunnel) || reverseTunnel.length === 0) {
|
|
1684
1724
|
return null;
|
|
1685
1725
|
}
|
|
1686
|
-
const forwarding
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
const
|
|
1695
|
-
if (
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1726
|
+
for (const forwarding of reverseTunnel) {
|
|
1727
|
+
const slicedForwarding = ipv6SafeSplitColon(forwarding);
|
|
1728
|
+
if (slicedForwarding.length === 3) {
|
|
1729
|
+
const parsed = parseDefaultForwarding(forwarding);
|
|
1730
|
+
if (parsed instanceof Error) return parsed;
|
|
1731
|
+
finalConfig.forwarding = `${parsed.localDomain}:${parsed.localPort}`;
|
|
1732
|
+
} else if (slicedForwarding.length === 4) {
|
|
1733
|
+
finalConfig.additionalForwarding ?? (finalConfig.additionalForwarding = []);
|
|
1734
|
+
const parsed = parseAdditionalForwarding(forwarding);
|
|
1735
|
+
if (parsed instanceof Error) return parsed;
|
|
1736
|
+
finalConfig.additionalForwarding.push(parsed);
|
|
1737
|
+
} else {
|
|
1738
|
+
return new Error(
|
|
1739
|
+
"Incorrect command line arguments: reverse tunnel address incorrect. Please use '-h' option for help."
|
|
1740
|
+
);
|
|
1699
1741
|
}
|
|
1700
1742
|
}
|
|
1701
1743
|
return null;
|
|
@@ -1737,10 +1779,10 @@ function parseArgs(finalConfig, remainingPositionals) {
|
|
|
1737
1779
|
}
|
|
1738
1780
|
function storeJson(config, saveconf) {
|
|
1739
1781
|
if (saveconf) {
|
|
1740
|
-
const
|
|
1782
|
+
const path5 = saveconf;
|
|
1741
1783
|
try {
|
|
1742
|
-
|
|
1743
|
-
logger.info(`Configuration saved to ${
|
|
1784
|
+
import_fs3.default.writeFileSync(path5, JSON.stringify(config, null, 2), { encoding: "utf-8", flag: "w" });
|
|
1785
|
+
logger.info(`Configuration saved to ${path5}`);
|
|
1744
1786
|
} catch (err) {
|
|
1745
1787
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1746
1788
|
logger.error("Error loading configuration:", msg);
|
|
@@ -1750,9 +1792,9 @@ function storeJson(config, saveconf) {
|
|
|
1750
1792
|
function loadJsonConfig(config) {
|
|
1751
1793
|
const configpath = config["conf"];
|
|
1752
1794
|
if (typeof configpath === "string" && configpath.trim().length > 0) {
|
|
1753
|
-
const filepath =
|
|
1795
|
+
const filepath = import_path3.default.resolve(configpath);
|
|
1754
1796
|
try {
|
|
1755
|
-
const data =
|
|
1797
|
+
const data = import_fs3.default.readFileSync(filepath, { encoding: "utf-8" });
|
|
1756
1798
|
const json = JSON.parse(data);
|
|
1757
1799
|
return json;
|
|
1758
1800
|
} catch (err) {
|
|
@@ -1820,24 +1862,24 @@ async function buildFinalConfig(values, positionals) {
|
|
|
1820
1862
|
storeJson(finalConfig, saveconf);
|
|
1821
1863
|
return finalConfig;
|
|
1822
1864
|
}
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1865
|
+
var import_pinggy3, import_fs3, import_path3, domainRegex, VALID_PROTOCOLS;
|
|
1866
|
+
var init_buildConfig = __esm({
|
|
1867
|
+
"src/cli/buildConfig.ts"() {
|
|
1868
|
+
"use strict";
|
|
1869
|
+
init_cjs_shims();
|
|
1870
|
+
init_defaults();
|
|
1871
|
+
init_extendedOptions();
|
|
1872
|
+
init_logger();
|
|
1873
|
+
init_util();
|
|
1874
|
+
import_pinggy3 = require("@pinggy/pinggy");
|
|
1875
|
+
import_fs3 = __toESM(require("fs"), 1);
|
|
1876
|
+
import_path3 = __toESM(require("path"), 1);
|
|
1877
|
+
domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
|
|
1878
|
+
VALID_PROTOCOLS = ["http", "tcp", "udp", "tls"];
|
|
1879
|
+
}
|
|
1880
|
+
});
|
|
1826
1881
|
|
|
1827
1882
|
// src/types.ts
|
|
1828
|
-
var ErrorCode = {
|
|
1829
|
-
InvalidRequestMethodError: "INVALID_REQUEST_METHOD",
|
|
1830
|
-
InvalidRequestBodyError: "COULD_NOT_READ_BODY",
|
|
1831
|
-
InternalServerError: "INTERNAL_SERVER_ERROR",
|
|
1832
|
-
InvalidBodyFormatError: "INVALID_DATA_FORMAT",
|
|
1833
|
-
ErrorStartingTunnel: "ERROR_STARTING_TUNNEL",
|
|
1834
|
-
TunnelNotFound: "TUNNEL_WITH_ID_OR_CONFIG_ID_NOT_FOUND",
|
|
1835
|
-
TunnelAlreadyRunningError: "TUNNEL_WITH_ID_OR_CONFIG_ID_ALREADY_RUNNING",
|
|
1836
|
-
WebsocketUpgradeFailError: "WEBSOCKET_UPGRADE_FAILED",
|
|
1837
|
-
RemoteManagementAlreadyRunning: "REMOTE_MANAGEMENT_ALREADY_RUNNING",
|
|
1838
|
-
RemoteManagementNotRunning: "REMOTE_MANAGEMENT_NOT_RUNNING",
|
|
1839
|
-
RemoteManagementDeserializationFailed: "REMOTE_MANAGEMENT_DESERIALIZATION_FAILED"
|
|
1840
|
-
};
|
|
1841
1883
|
function isErrorResponse(obj) {
|
|
1842
1884
|
return typeof obj === "object" && obj !== null && "code" in obj && "message" in obj && typeof obj.message === "string" && Object.values(ErrorCode).includes(obj.code);
|
|
1843
1885
|
}
|
|
@@ -1900,93 +1942,36 @@ function newStats() {
|
|
|
1900
1942
|
elapsedTime: 0
|
|
1901
1943
|
};
|
|
1902
1944
|
}
|
|
1903
|
-
var RemoteManagementStatus
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
allowpreflight: import_zod.z.boolean().optional(),
|
|
1930
|
-
// legacy key
|
|
1931
|
-
autoreconnect: import_zod.z.boolean(),
|
|
1932
|
-
basicauth: import_zod.z.array(import_zod.z.object({ username: import_zod.z.string(), password: import_zod.z.string() })).nullable(),
|
|
1933
|
-
bearerauth: import_zod.z.string().nullable(),
|
|
1934
|
-
configid: import_zod.z.string(),
|
|
1935
|
-
configname: import_zod.z.string(),
|
|
1936
|
-
greetmsg: import_zod.z.string().optional(),
|
|
1937
|
-
force: import_zod.z.boolean(),
|
|
1938
|
-
forwardedhost: import_zod.z.string(),
|
|
1939
|
-
fullRequestUrl: import_zod.z.boolean(),
|
|
1940
|
-
headermodification: import_zod.z.array(HeaderModificationSchema),
|
|
1941
|
-
httpsOnly: import_zod.z.boolean(),
|
|
1942
|
-
internalwebdebuggerport: import_zod.z.number(),
|
|
1943
|
-
ipwhitelist: import_zod.z.array(import_zod.z.string()).nullable(),
|
|
1944
|
-
localport: import_zod.z.number(),
|
|
1945
|
-
localsservertls: import_zod.z.union([import_zod.z.boolean(), import_zod.z.string()]),
|
|
1946
|
-
localservertlssni: import_zod.z.string().nullable(),
|
|
1947
|
-
regioncode: import_zod.z.string(),
|
|
1948
|
-
noReverseProxy: import_zod.z.boolean(),
|
|
1949
|
-
serveraddress: import_zod.z.string(),
|
|
1950
|
-
serverport: import_zod.z.number(),
|
|
1951
|
-
statusCheckInterval: import_zod.z.number(),
|
|
1952
|
-
token: import_zod.z.string(),
|
|
1953
|
-
tunnelTimeout: import_zod.z.number(),
|
|
1954
|
-
type: import_zod.z.enum([
|
|
1955
|
-
import_pinggy4.TunnelType.Http,
|
|
1956
|
-
import_pinggy4.TunnelType.Tcp,
|
|
1957
|
-
import_pinggy4.TunnelType.Udp,
|
|
1958
|
-
import_pinggy4.TunnelType.Tls,
|
|
1959
|
-
import_pinggy4.TunnelType.TlsTcp
|
|
1960
|
-
]),
|
|
1961
|
-
webdebuggerport: import_zod.z.number(),
|
|
1962
|
-
xff: import_zod.z.string(),
|
|
1963
|
-
additionalForwarding: import_zod.z.array(AdditionalForwardingSchema).optional(),
|
|
1964
|
-
serve: import_zod.z.string().optional()
|
|
1965
|
-
}).superRefine((data, ctx) => {
|
|
1966
|
-
if (data.allowPreflight === void 0 && data.allowpreflight === void 0) {
|
|
1967
|
-
ctx.addIssue({
|
|
1968
|
-
code: "custom",
|
|
1969
|
-
message: "Either allowPreflight or allowpreflight is required",
|
|
1970
|
-
path: ["allowPreflight"]
|
|
1971
|
-
});
|
|
1945
|
+
var ErrorCode, RemoteManagementStatus;
|
|
1946
|
+
var init_types = __esm({
|
|
1947
|
+
"src/types.ts"() {
|
|
1948
|
+
"use strict";
|
|
1949
|
+
init_cjs_shims();
|
|
1950
|
+
ErrorCode = {
|
|
1951
|
+
InvalidRequestMethodError: "INVALID_REQUEST_METHOD",
|
|
1952
|
+
InvalidRequestBodyError: "COULD_NOT_READ_BODY",
|
|
1953
|
+
InternalServerError: "INTERNAL_SERVER_ERROR",
|
|
1954
|
+
InvalidBodyFormatError: "INVALID_DATA_FORMAT",
|
|
1955
|
+
ErrorStartingTunnel: "ERROR_STARTING_TUNNEL",
|
|
1956
|
+
TunnelNotFound: "TUNNEL_WITH_ID_OR_CONFIG_ID_NOT_FOUND",
|
|
1957
|
+
TunnelAlreadyRunningError: "TUNNEL_WITH_ID_OR_CONFIG_ID_ALREADY_RUNNING",
|
|
1958
|
+
WebsocketUpgradeFailError: "WEBSOCKET_UPGRADE_FAILED",
|
|
1959
|
+
RemoteManagementAlreadyRunning: "REMOTE_MANAGEMENT_ALREADY_RUNNING",
|
|
1960
|
+
RemoteManagementNotRunning: "REMOTE_MANAGEMENT_NOT_RUNNING",
|
|
1961
|
+
RemoteManagementDeserializationFailed: "REMOTE_MANAGEMENT_DESERIALIZATION_FAILED"
|
|
1962
|
+
};
|
|
1963
|
+
RemoteManagementStatus = {
|
|
1964
|
+
Connecting: "CONNECTING",
|
|
1965
|
+
Disconnecting: "DISCONNECTING",
|
|
1966
|
+
Reconnecting: "RECONNECTING",
|
|
1967
|
+
Running: "RUNNING",
|
|
1968
|
+
NotRunning: "NOT_RUNNING",
|
|
1969
|
+
Error: "ERROR"
|
|
1970
|
+
};
|
|
1972
1971
|
}
|
|
1973
|
-
}).transform((data) => ({
|
|
1974
|
-
...data,
|
|
1975
|
-
allowPreflight: data.allowPreflight ?? data.allowpreflight,
|
|
1976
|
-
allowpreflight: data.allowPreflight ?? data.allowpreflight
|
|
1977
|
-
}));
|
|
1978
|
-
var StartSchema = import_zod.z.object({
|
|
1979
|
-
tunnelID: import_zod.z.string().nullable().optional(),
|
|
1980
|
-
tunnelConfig: TunnelConfigSchema
|
|
1981
|
-
});
|
|
1982
|
-
var StopSchema = import_zod.z.object({
|
|
1983
|
-
tunnelID: import_zod.z.string().min(1)
|
|
1984
|
-
});
|
|
1985
|
-
var GetSchema = StopSchema;
|
|
1986
|
-
var RestartSchema = StopSchema;
|
|
1987
|
-
var UpdateConfigSchema = import_zod.z.object({
|
|
1988
|
-
tunnelConfig: TunnelConfigSchema
|
|
1989
1972
|
});
|
|
1973
|
+
|
|
1974
|
+
// src/remote_management/remote_schema.ts
|
|
1990
1975
|
function tunnelConfigToPinggyOptions(config) {
|
|
1991
1976
|
return {
|
|
1992
1977
|
token: config.token || "",
|
|
@@ -2049,313 +2034,279 @@ function pinggyOptionsToTunnelConfig(opts, configid, configName, localserverTls,
|
|
|
2049
2034
|
serve: serve || ""
|
|
2050
2035
|
};
|
|
2051
2036
|
}
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
if (managed) {
|
|
2064
|
-
status.createdtimestamp = managed.createdAt || "";
|
|
2065
|
-
status.starttimestamp = managed.startedAt || "";
|
|
2066
|
-
status.endtimestamp = managed.stoppedAt || "";
|
|
2067
|
-
}
|
|
2068
|
-
} catch (e) {
|
|
2069
|
-
}
|
|
2070
|
-
return status;
|
|
2071
|
-
}
|
|
2072
|
-
// --- Helper to construct TunnelResponse ---
|
|
2073
|
-
async buildTunnelResponse(tunnelid, tunnelConfig, configid, tunnelName, additionalForwarding, serve) {
|
|
2074
|
-
const [status, stats, tlsInfo, greetMsg, remoteurls] = await Promise.all([
|
|
2075
|
-
this.tunnelManager.getTunnelStatus(tunnelid),
|
|
2076
|
-
this.tunnelManager.getLatestTunnelStats(tunnelid) || newStats(),
|
|
2077
|
-
this.tunnelManager.getLocalserverTlsInfo(tunnelid),
|
|
2078
|
-
this.tunnelManager.getTunnelGreetMessage(tunnelid),
|
|
2079
|
-
this.tunnelManager.getTunnelUrls(tunnelid)
|
|
2080
|
-
]);
|
|
2081
|
-
return {
|
|
2082
|
-
tunnelid,
|
|
2083
|
-
remoteurls,
|
|
2084
|
-
tunnelconfig: pinggyOptionsToTunnelConfig(tunnelConfig, configid, tunnelName, tlsInfo, greetMsg, additionalForwarding),
|
|
2085
|
-
status: this.buildStatus(tunnelid, status, "" /* NoError */),
|
|
2086
|
-
stats
|
|
2087
|
-
};
|
|
2088
|
-
}
|
|
2089
|
-
error(code, err, fallback) {
|
|
2090
|
-
return newErrorResponse({
|
|
2091
|
-
code,
|
|
2092
|
-
message: err instanceof Error ? err.message : fallback
|
|
2037
|
+
var import_pinggy4, import_zod, HeaderModificationSchema, AdditionalForwardingSchema, TunnelConfigSchema, StartSchema, StopSchema, GetSchema, RestartSchema, UpdateConfigSchema;
|
|
2038
|
+
var init_remote_schema = __esm({
|
|
2039
|
+
"src/remote_management/remote_schema.ts"() {
|
|
2040
|
+
"use strict";
|
|
2041
|
+
init_cjs_shims();
|
|
2042
|
+
import_pinggy4 = require("@pinggy/pinggy");
|
|
2043
|
+
import_zod = require("zod");
|
|
2044
|
+
HeaderModificationSchema = import_zod.z.object({
|
|
2045
|
+
key: import_zod.z.string(),
|
|
2046
|
+
value: import_zod.z.array(import_zod.z.string()).optional(),
|
|
2047
|
+
type: import_zod.z.enum(["add", "remove", "update"])
|
|
2093
2048
|
});
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
this.tunnelManager.getTunnelStatus(t.tunnelid),
|
|
2147
|
-
this.tunnelManager.getLocalserverTlsInfo(t.tunnelid),
|
|
2148
|
-
this.tunnelManager.getTunnelGreetMessage(t.tunnelid)
|
|
2149
|
-
]);
|
|
2150
|
-
const pinggyOptions = status !== "closed" /* Closed */ && status !== "exited" /* Exited */ ? await this.tunnelManager.getTunnelConfig("", t.tunnelid) : t.tunnelConfig;
|
|
2151
|
-
const tunnelConfig = pinggyOptionsToTunnelConfig(pinggyOptions, t.configid, t.tunnelName, tlsInfo, greetMsg, t.additionalForwarding, t.serve);
|
|
2152
|
-
return {
|
|
2153
|
-
tunnelid: t.tunnelid,
|
|
2154
|
-
remoteurls: t.remoteurls,
|
|
2155
|
-
status: this.buildStatus(t.tunnelid, status, "" /* NoError */),
|
|
2156
|
-
stats: rawStats,
|
|
2157
|
-
tunnelconfig: tunnelConfig
|
|
2158
|
-
};
|
|
2159
|
-
})
|
|
2160
|
-
);
|
|
2161
|
-
} catch (err) {
|
|
2162
|
-
return this.error(ErrorCode.InternalServerError, err, "Failed to list tunnels");
|
|
2163
|
-
}
|
|
2164
|
-
}
|
|
2165
|
-
async handleStop(tunnelid) {
|
|
2166
|
-
try {
|
|
2167
|
-
const { configid } = this.tunnelManager.stopTunnel(tunnelid);
|
|
2168
|
-
const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
|
|
2169
|
-
if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
|
|
2170
|
-
return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, configid, managed.tunnelName, managed.additionalForwarding, managed.serve);
|
|
2171
|
-
} catch (err) {
|
|
2172
|
-
return this.error(ErrorCode.TunnelNotFound, err, "Failed to stop tunnel");
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2175
|
-
async handleGet(tunnelid) {
|
|
2176
|
-
try {
|
|
2177
|
-
const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
|
|
2178
|
-
if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
|
|
2179
|
-
return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, managed.configid, managed.tunnelName, managed.additionalForwarding, managed.serve);
|
|
2180
|
-
} catch (err) {
|
|
2181
|
-
return this.error(ErrorCode.TunnelNotFound, err, "Failed to get tunnel information");
|
|
2182
|
-
}
|
|
2183
|
-
}
|
|
2184
|
-
async handleRestart(tunnelid) {
|
|
2185
|
-
try {
|
|
2186
|
-
await this.tunnelManager.restartTunnel(tunnelid);
|
|
2187
|
-
const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
|
|
2188
|
-
if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
|
|
2189
|
-
return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, managed.configid, managed.tunnelName, managed.additionalForwarding, managed.serve);
|
|
2190
|
-
} catch (err) {
|
|
2191
|
-
return this.error(ErrorCode.TunnelNotFound, err, "Failed to restart tunnel");
|
|
2192
|
-
}
|
|
2193
|
-
}
|
|
2194
|
-
handleRegisterStatsListener(tunnelid, listener) {
|
|
2195
|
-
this.tunnelManager.registerStatsListener(tunnelid, listener);
|
|
2196
|
-
}
|
|
2197
|
-
handleUnregisterStatsListener(tunnelid, listnerId) {
|
|
2198
|
-
this.tunnelManager.deregisterStatsListener(tunnelid, listnerId);
|
|
2199
|
-
}
|
|
2200
|
-
handleGetTunnelStats(tunnelid) {
|
|
2201
|
-
try {
|
|
2202
|
-
const stats = this.tunnelManager.getTunnelStats(tunnelid);
|
|
2203
|
-
if (!stats) {
|
|
2204
|
-
return [newStats()];
|
|
2049
|
+
AdditionalForwardingSchema = import_zod.z.object({
|
|
2050
|
+
remoteDomain: import_zod.z.string().optional(),
|
|
2051
|
+
remotePort: import_zod.z.number().optional(),
|
|
2052
|
+
localDomain: import_zod.z.string(),
|
|
2053
|
+
localPort: import_zod.z.number()
|
|
2054
|
+
});
|
|
2055
|
+
TunnelConfigSchema = import_zod.z.object({
|
|
2056
|
+
allowPreflight: import_zod.z.boolean().optional(),
|
|
2057
|
+
// primary key
|
|
2058
|
+
allowpreflight: import_zod.z.boolean().optional(),
|
|
2059
|
+
// legacy key
|
|
2060
|
+
autoreconnect: import_zod.z.boolean(),
|
|
2061
|
+
basicauth: import_zod.z.array(import_zod.z.object({ username: import_zod.z.string(), password: import_zod.z.string() })).nullable(),
|
|
2062
|
+
bearerauth: import_zod.z.string().nullable(),
|
|
2063
|
+
configid: import_zod.z.string(),
|
|
2064
|
+
configname: import_zod.z.string(),
|
|
2065
|
+
greetmsg: import_zod.z.string().optional(),
|
|
2066
|
+
force: import_zod.z.boolean(),
|
|
2067
|
+
forwardedhost: import_zod.z.string(),
|
|
2068
|
+
fullRequestUrl: import_zod.z.boolean(),
|
|
2069
|
+
headermodification: import_zod.z.array(HeaderModificationSchema),
|
|
2070
|
+
httpsOnly: import_zod.z.boolean(),
|
|
2071
|
+
internalwebdebuggerport: import_zod.z.number(),
|
|
2072
|
+
ipwhitelist: import_zod.z.array(import_zod.z.string()).nullable(),
|
|
2073
|
+
localport: import_zod.z.number(),
|
|
2074
|
+
localsservertls: import_zod.z.union([import_zod.z.boolean(), import_zod.z.string()]),
|
|
2075
|
+
localservertlssni: import_zod.z.string().nullable(),
|
|
2076
|
+
regioncode: import_zod.z.string(),
|
|
2077
|
+
noReverseProxy: import_zod.z.boolean(),
|
|
2078
|
+
serveraddress: import_zod.z.string(),
|
|
2079
|
+
serverport: import_zod.z.number(),
|
|
2080
|
+
statusCheckInterval: import_zod.z.number(),
|
|
2081
|
+
token: import_zod.z.string(),
|
|
2082
|
+
tunnelTimeout: import_zod.z.number(),
|
|
2083
|
+
type: import_zod.z.enum([
|
|
2084
|
+
import_pinggy4.TunnelType.Http,
|
|
2085
|
+
import_pinggy4.TunnelType.Tcp,
|
|
2086
|
+
import_pinggy4.TunnelType.Udp,
|
|
2087
|
+
import_pinggy4.TunnelType.Tls,
|
|
2088
|
+
import_pinggy4.TunnelType.TlsTcp
|
|
2089
|
+
]),
|
|
2090
|
+
webdebuggerport: import_zod.z.number(),
|
|
2091
|
+
xff: import_zod.z.string(),
|
|
2092
|
+
additionalForwarding: import_zod.z.array(AdditionalForwardingSchema).optional(),
|
|
2093
|
+
serve: import_zod.z.string().optional()
|
|
2094
|
+
}).superRefine((data, ctx) => {
|
|
2095
|
+
if (data.allowPreflight === void 0 && data.allowpreflight === void 0) {
|
|
2096
|
+
ctx.addIssue({
|
|
2097
|
+
code: "custom",
|
|
2098
|
+
message: "Either allowPreflight or allowpreflight is required",
|
|
2099
|
+
path: ["allowPreflight"]
|
|
2100
|
+
});
|
|
2205
2101
|
}
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
}
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
return this.tunnelManager.removeStoppedTunnelByTunnelId(tunnelId);
|
|
2224
|
-
} catch (err) {
|
|
2225
|
-
return this.error(ErrorCode.InternalServerError, err, "Failed to remove stopped tunnel by tunnelId");
|
|
2226
|
-
}
|
|
2102
|
+
}).transform((data) => ({
|
|
2103
|
+
...data,
|
|
2104
|
+
allowPreflight: data.allowPreflight ?? data.allowpreflight,
|
|
2105
|
+
allowpreflight: data.allowPreflight ?? data.allowpreflight
|
|
2106
|
+
}));
|
|
2107
|
+
StartSchema = import_zod.z.object({
|
|
2108
|
+
tunnelID: import_zod.z.string().nullable().optional(),
|
|
2109
|
+
tunnelConfig: TunnelConfigSchema
|
|
2110
|
+
});
|
|
2111
|
+
StopSchema = import_zod.z.object({
|
|
2112
|
+
tunnelID: import_zod.z.string().min(1)
|
|
2113
|
+
});
|
|
2114
|
+
GetSchema = StopSchema;
|
|
2115
|
+
RestartSchema = StopSchema;
|
|
2116
|
+
UpdateConfigSchema = import_zod.z.object({
|
|
2117
|
+
tunnelConfig: TunnelConfigSchema
|
|
2118
|
+
});
|
|
2227
2119
|
}
|
|
2228
|
-
};
|
|
2120
|
+
});
|
|
2229
2121
|
|
|
2230
|
-
// src/remote_management/
|
|
2231
|
-
var
|
|
2232
|
-
var
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
resp.command = req.command || "";
|
|
2255
|
-
resp.requestid = req.requestid || "";
|
|
2256
|
-
this.sendResponse(ws, resp);
|
|
2257
|
-
}
|
|
2258
|
-
async handleStartReq(req, raw) {
|
|
2259
|
-
const dc = StartSchema.parse(raw);
|
|
2260
|
-
printer_default.info("Starting tunnel with config name: " + dc.tunnelConfig.configname);
|
|
2261
|
-
const result = await this.tunnelHandler.handleStart(dc.tunnelConfig);
|
|
2262
|
-
return this.wrapResponse(result, req);
|
|
2263
|
-
}
|
|
2264
|
-
async handleStopReq(req, raw) {
|
|
2265
|
-
const dc = StopSchema.parse(raw);
|
|
2266
|
-
printer_default.info("Stopping tunnel with ID: " + dc.tunnelID);
|
|
2267
|
-
const result = await this.tunnelHandler.handleStop(dc.tunnelID);
|
|
2268
|
-
return this.wrapResponse(result, req);
|
|
2269
|
-
}
|
|
2270
|
-
async handleGetReq(req, raw) {
|
|
2271
|
-
const dc = GetSchema.parse(raw);
|
|
2272
|
-
const result = await this.tunnelHandler.handleGet(dc.tunnelID);
|
|
2273
|
-
return this.wrapResponse(result, req);
|
|
2274
|
-
}
|
|
2275
|
-
async handleRestartReq(req, raw) {
|
|
2276
|
-
const dc = RestartSchema.parse(raw);
|
|
2277
|
-
const result = await this.tunnelHandler.handleRestart(dc.tunnelID);
|
|
2278
|
-
return this.wrapResponse(result, req);
|
|
2279
|
-
}
|
|
2280
|
-
async handleUpdateConfigReq(req, raw) {
|
|
2281
|
-
const dc = UpdateConfigSchema.parse(raw);
|
|
2282
|
-
const result = await this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig);
|
|
2283
|
-
return this.wrapResponse(result, req);
|
|
2284
|
-
}
|
|
2285
|
-
async handleListReq(req) {
|
|
2286
|
-
const result = await this.tunnelHandler.handleList();
|
|
2287
|
-
return this.wrapResponse(result, req);
|
|
2288
|
-
}
|
|
2289
|
-
wrapResponse(result, req) {
|
|
2290
|
-
if (isErrorResponse(result)) {
|
|
2291
|
-
const errResp = NewErrorResponseObject(result);
|
|
2292
|
-
errResp.command = req.command;
|
|
2293
|
-
errResp.requestid = req.requestid;
|
|
2294
|
-
return errResp;
|
|
2295
|
-
}
|
|
2296
|
-
const finalResult = JSON.parse(JSON.stringify(result));
|
|
2297
|
-
if (Array.isArray(finalResult)) {
|
|
2298
|
-
finalResult.forEach((item) => {
|
|
2299
|
-
if (item?.tunnelconfig) {
|
|
2300
|
-
delete item.tunnelconfig.allowPreflight;
|
|
2122
|
+
// src/remote_management/handler.ts
|
|
2123
|
+
var import_pinggy5, TunnelOperations;
|
|
2124
|
+
var init_handler = __esm({
|
|
2125
|
+
"src/remote_management/handler.ts"() {
|
|
2126
|
+
"use strict";
|
|
2127
|
+
init_cjs_shims();
|
|
2128
|
+
init_types();
|
|
2129
|
+
init_TunnelManager();
|
|
2130
|
+
init_remote_schema();
|
|
2131
|
+
import_pinggy5 = require("@pinggy/pinggy");
|
|
2132
|
+
TunnelOperations = class {
|
|
2133
|
+
constructor() {
|
|
2134
|
+
this.tunnelManager = TunnelManager.getInstance();
|
|
2135
|
+
}
|
|
2136
|
+
buildStatus(tunnelId, state, errorCode) {
|
|
2137
|
+
const status = newStatus(state, errorCode, "");
|
|
2138
|
+
try {
|
|
2139
|
+
const managed = this.tunnelManager.getManagedTunnel("", tunnelId);
|
|
2140
|
+
if (managed) {
|
|
2141
|
+
status.createdtimestamp = managed.createdAt || "";
|
|
2142
|
+
status.starttimestamp = managed.startedAt || "";
|
|
2143
|
+
status.endtimestamp = managed.stoppedAt || "";
|
|
2144
|
+
}
|
|
2145
|
+
} catch (e) {
|
|
2301
2146
|
}
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2147
|
+
return status;
|
|
2148
|
+
}
|
|
2149
|
+
// --- Helper to construct TunnelResponse ---
|
|
2150
|
+
async buildTunnelResponse(tunnelid, tunnelConfig, configid, tunnelName, additionalForwarding, serve) {
|
|
2151
|
+
const [status, stats, tlsInfo, greetMsg, remoteurls] = await Promise.all([
|
|
2152
|
+
this.tunnelManager.getTunnelStatus(tunnelid),
|
|
2153
|
+
this.tunnelManager.getLatestTunnelStats(tunnelid) || newStats(),
|
|
2154
|
+
this.tunnelManager.getLocalserverTlsInfo(tunnelid),
|
|
2155
|
+
this.tunnelManager.getTunnelGreetMessage(tunnelid),
|
|
2156
|
+
this.tunnelManager.getTunnelUrls(tunnelid)
|
|
2157
|
+
]);
|
|
2158
|
+
return {
|
|
2159
|
+
tunnelid,
|
|
2160
|
+
remoteurls,
|
|
2161
|
+
tunnelconfig: pinggyOptionsToTunnelConfig(tunnelConfig, configid, tunnelName, tlsInfo, greetMsg, additionalForwarding),
|
|
2162
|
+
status: this.buildStatus(tunnelid, status, "" /* NoError */),
|
|
2163
|
+
stats
|
|
2164
|
+
};
|
|
2165
|
+
}
|
|
2166
|
+
error(code, err, fallback) {
|
|
2167
|
+
return newErrorResponse({
|
|
2168
|
+
code,
|
|
2169
|
+
message: err instanceof Error ? err.message : fallback
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2172
|
+
// --- Operations ---
|
|
2173
|
+
async handleStart(config) {
|
|
2174
|
+
try {
|
|
2175
|
+
const opts = tunnelConfigToPinggyOptions(config);
|
|
2176
|
+
const additionalForwardingParsed = config.additionalForwarding || [];
|
|
2177
|
+
const { tunnelid, instance, tunnelName, additionalForwarding, serve } = await this.tunnelManager.createTunnel({
|
|
2178
|
+
...opts,
|
|
2179
|
+
tunnelType: Array.isArray(config.type) ? config.type : config.type ? [config.type] : [import_pinggy5.TunnelType.Http],
|
|
2180
|
+
// Temporary fix in future we will not use this field.
|
|
2181
|
+
configid: config.configid,
|
|
2182
|
+
tunnelName: config.configname,
|
|
2183
|
+
additionalForwarding: additionalForwardingParsed,
|
|
2184
|
+
serve: config.serve
|
|
2185
|
+
});
|
|
2186
|
+
this.tunnelManager.startTunnel(tunnelid);
|
|
2187
|
+
const tunnelPconfig = await this.tunnelManager.getTunnelConfig("", tunnelid);
|
|
2188
|
+
const resp = this.buildTunnelResponse(tunnelid, tunnelPconfig, config.configid, tunnelName, additionalForwarding, serve);
|
|
2189
|
+
return resp;
|
|
2190
|
+
} catch (err) {
|
|
2191
|
+
return this.error(ErrorCode.ErrorStartingTunnel, err, "Unknown error occurred while starting tunnel");
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
async handleUpdateConfig(config) {
|
|
2195
|
+
try {
|
|
2196
|
+
const opts = tunnelConfigToPinggyOptions(config);
|
|
2197
|
+
const tunnel = await this.tunnelManager.updateConfig({
|
|
2198
|
+
...opts,
|
|
2199
|
+
tunnelType: Array.isArray(config.type) ? config.type : config.type ? [config.type] : [import_pinggy5.TunnelType.Http],
|
|
2200
|
+
// // Temporary fix in future we will not use this field.
|
|
2201
|
+
configid: config.configid,
|
|
2202
|
+
tunnelName: config.configname,
|
|
2203
|
+
additionalForwarding: config.additionalForwarding || [],
|
|
2204
|
+
serve: config.serve
|
|
2205
|
+
});
|
|
2206
|
+
if (!tunnel.instance || !tunnel.tunnelConfig)
|
|
2207
|
+
throw new Error("Invalid tunnel state after configuration update");
|
|
2208
|
+
return this.buildTunnelResponse(tunnel.tunnelid, tunnel.tunnelConfig, config.configid, tunnel.tunnelName, tunnel.additionalForwarding, tunnel.serve);
|
|
2209
|
+
} catch (err) {
|
|
2210
|
+
return this.error(ErrorCode.InternalServerError, err, "Failed to update tunnel configuration");
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
async handleList() {
|
|
2214
|
+
try {
|
|
2215
|
+
const tunnels = await this.tunnelManager.getAllTunnels();
|
|
2216
|
+
if (tunnels.length === 0) {
|
|
2217
|
+
return [];
|
|
2218
|
+
}
|
|
2219
|
+
return Promise.all(
|
|
2220
|
+
tunnels.map(async (t) => {
|
|
2221
|
+
const rawStats = this.tunnelManager.getLatestTunnelStats(t.tunnelid) || newStats();
|
|
2222
|
+
const [status, tlsInfo, greetMsg] = await Promise.all([
|
|
2223
|
+
this.tunnelManager.getTunnelStatus(t.tunnelid),
|
|
2224
|
+
this.tunnelManager.getLocalserverTlsInfo(t.tunnelid),
|
|
2225
|
+
this.tunnelManager.getTunnelGreetMessage(t.tunnelid)
|
|
2226
|
+
]);
|
|
2227
|
+
const pinggyOptions = status !== "closed" /* Closed */ && status !== "exited" /* Exited */ ? await this.tunnelManager.getTunnelConfig("", t.tunnelid) : t.tunnelConfig;
|
|
2228
|
+
const tunnelConfig = pinggyOptionsToTunnelConfig(pinggyOptions, t.configid, t.tunnelName, tlsInfo, greetMsg, t.additionalForwarding, t.serve);
|
|
2229
|
+
return {
|
|
2230
|
+
tunnelid: t.tunnelid,
|
|
2231
|
+
remoteurls: t.remoteurls,
|
|
2232
|
+
status: this.buildStatus(t.tunnelid, status, "" /* NoError */),
|
|
2233
|
+
stats: rawStats,
|
|
2234
|
+
tunnelconfig: tunnelConfig
|
|
2235
|
+
};
|
|
2236
|
+
})
|
|
2237
|
+
);
|
|
2238
|
+
} catch (err) {
|
|
2239
|
+
return this.error(ErrorCode.InternalServerError, err, "Failed to list tunnels");
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
async handleStop(tunnelid) {
|
|
2243
|
+
try {
|
|
2244
|
+
const { configid } = this.tunnelManager.stopTunnel(tunnelid);
|
|
2245
|
+
const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
|
|
2246
|
+
if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
|
|
2247
|
+
return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, configid, managed.tunnelName, managed.additionalForwarding, managed.serve);
|
|
2248
|
+
} catch (err) {
|
|
2249
|
+
return this.error(ErrorCode.TunnelNotFound, err, "Failed to stop tunnel");
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
async handleGet(tunnelid) {
|
|
2253
|
+
try {
|
|
2254
|
+
const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
|
|
2255
|
+
if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
|
|
2256
|
+
return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, managed.configid, managed.tunnelName, managed.additionalForwarding, managed.serve);
|
|
2257
|
+
} catch (err) {
|
|
2258
|
+
return this.error(ErrorCode.TunnelNotFound, err, "Failed to get tunnel information");
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
async handleRestart(tunnelid) {
|
|
2262
|
+
try {
|
|
2263
|
+
await this.tunnelManager.restartTunnel(tunnelid);
|
|
2264
|
+
const managed = this.tunnelManager.getManagedTunnel("", tunnelid);
|
|
2265
|
+
if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
|
|
2266
|
+
return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, managed.configid, managed.tunnelName, managed.additionalForwarding, managed.serve);
|
|
2267
|
+
} catch (err) {
|
|
2268
|
+
return this.error(ErrorCode.TunnelNotFound, err, "Failed to restart tunnel");
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
handleRegisterStatsListener(tunnelid, listener) {
|
|
2272
|
+
this.tunnelManager.registerStatsListener(tunnelid, listener);
|
|
2273
|
+
}
|
|
2274
|
+
handleUnregisterStatsListener(tunnelid, listnerId) {
|
|
2275
|
+
this.tunnelManager.deregisterStatsListener(tunnelid, listnerId);
|
|
2276
|
+
}
|
|
2277
|
+
handleGetTunnelStats(tunnelid) {
|
|
2278
|
+
try {
|
|
2279
|
+
const stats = this.tunnelManager.getTunnelStats(tunnelid);
|
|
2280
|
+
if (!stats) {
|
|
2281
|
+
return [newStats()];
|
|
2344
2282
|
}
|
|
2345
|
-
return
|
|
2283
|
+
return stats;
|
|
2284
|
+
} catch (err) {
|
|
2285
|
+
return this.error(ErrorCode.TunnelNotFound, err, "Failed to get tunnel stats");
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
handleRegisterDisconnectListener(tunnelid, listener) {
|
|
2289
|
+
this.tunnelManager.registerDisconnectListener(tunnelid, listener);
|
|
2290
|
+
}
|
|
2291
|
+
handleRemoveStoppedTunnelByConfigId(configId) {
|
|
2292
|
+
try {
|
|
2293
|
+
return this.tunnelManager.removeStoppedTunnelByConfigId(configId);
|
|
2294
|
+
} catch (err) {
|
|
2295
|
+
return this.error(ErrorCode.InternalServerError, err, "Failed to remove stopped tunnel by configId");
|
|
2296
|
+
}
|
|
2346
2297
|
}
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2298
|
+
handleRemoveStoppedTunnelByTunnelId(tunnelId) {
|
|
2299
|
+
try {
|
|
2300
|
+
return this.tunnelManager.removeStoppedTunnelByTunnelId(tunnelId);
|
|
2301
|
+
} catch (err) {
|
|
2302
|
+
return this.error(ErrorCode.InternalServerError, err, "Failed to remove stopped tunnel by tunnelId");
|
|
2303
|
+
}
|
|
2353
2304
|
}
|
|
2354
|
-
|
|
2355
|
-
return this.sendError(ws, req, e?.message || "Internal error");
|
|
2356
|
-
}
|
|
2305
|
+
};
|
|
2357
2306
|
}
|
|
2358
|
-
};
|
|
2307
|
+
});
|
|
2308
|
+
|
|
2309
|
+
// src/remote_management/websocket_handlers.ts
|
|
2359
2310
|
function handleConnectionStatusMessage(firstMessage) {
|
|
2360
2311
|
try {
|
|
2361
2312
|
const text = typeof firstMessage === "string" ? firstMessage : firstMessage.toString();
|
|
@@ -2372,16 +2323,148 @@ function handleConnectionStatusMessage(firstMessage) {
|
|
|
2372
2323
|
return true;
|
|
2373
2324
|
}
|
|
2374
2325
|
}
|
|
2326
|
+
var import_zod2, WebSocketCommandHandler;
|
|
2327
|
+
var init_websocket_handlers = __esm({
|
|
2328
|
+
"src/remote_management/websocket_handlers.ts"() {
|
|
2329
|
+
"use strict";
|
|
2330
|
+
init_cjs_shims();
|
|
2331
|
+
init_logger();
|
|
2332
|
+
init_types();
|
|
2333
|
+
init_handler();
|
|
2334
|
+
init_remote_schema();
|
|
2335
|
+
import_zod2 = __toESM(require("zod"), 1);
|
|
2336
|
+
init_printer();
|
|
2337
|
+
WebSocketCommandHandler = class {
|
|
2338
|
+
constructor() {
|
|
2339
|
+
this.tunnelHandler = new TunnelOperations();
|
|
2340
|
+
}
|
|
2341
|
+
safeParse(text) {
|
|
2342
|
+
if (!text) return void 0;
|
|
2343
|
+
try {
|
|
2344
|
+
return JSON.parse(text);
|
|
2345
|
+
} catch (e) {
|
|
2346
|
+
logger.warn("Invalid JSON payload", { error: String(e), text });
|
|
2347
|
+
return void 0;
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
sendResponse(ws, resp) {
|
|
2351
|
+
const payload = {
|
|
2352
|
+
...resp,
|
|
2353
|
+
response: Buffer.from(resp.response || []).toString("base64")
|
|
2354
|
+
};
|
|
2355
|
+
ws.send(JSON.stringify(payload));
|
|
2356
|
+
}
|
|
2357
|
+
sendError(ws, req, message, code = ErrorCode.InternalServerError) {
|
|
2358
|
+
const resp = NewErrorResponseObject({ code, message });
|
|
2359
|
+
resp.command = req.command || "";
|
|
2360
|
+
resp.requestid = req.requestid || "";
|
|
2361
|
+
this.sendResponse(ws, resp);
|
|
2362
|
+
}
|
|
2363
|
+
async handleStartReq(req, raw) {
|
|
2364
|
+
const dc = StartSchema.parse(raw);
|
|
2365
|
+
printer_default.info("Starting tunnel with config name: " + dc.tunnelConfig.configname);
|
|
2366
|
+
const result = await this.tunnelHandler.handleStart(dc.tunnelConfig);
|
|
2367
|
+
return this.wrapResponse(result, req);
|
|
2368
|
+
}
|
|
2369
|
+
async handleStopReq(req, raw) {
|
|
2370
|
+
const dc = StopSchema.parse(raw);
|
|
2371
|
+
printer_default.info("Stopping tunnel with ID: " + dc.tunnelID);
|
|
2372
|
+
const result = await this.tunnelHandler.handleStop(dc.tunnelID);
|
|
2373
|
+
return this.wrapResponse(result, req);
|
|
2374
|
+
}
|
|
2375
|
+
async handleGetReq(req, raw) {
|
|
2376
|
+
const dc = GetSchema.parse(raw);
|
|
2377
|
+
const result = await this.tunnelHandler.handleGet(dc.tunnelID);
|
|
2378
|
+
return this.wrapResponse(result, req);
|
|
2379
|
+
}
|
|
2380
|
+
async handleRestartReq(req, raw) {
|
|
2381
|
+
const dc = RestartSchema.parse(raw);
|
|
2382
|
+
const result = await this.tunnelHandler.handleRestart(dc.tunnelID);
|
|
2383
|
+
return this.wrapResponse(result, req);
|
|
2384
|
+
}
|
|
2385
|
+
async handleUpdateConfigReq(req, raw) {
|
|
2386
|
+
const dc = UpdateConfigSchema.parse(raw);
|
|
2387
|
+
const result = await this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig);
|
|
2388
|
+
return this.wrapResponse(result, req);
|
|
2389
|
+
}
|
|
2390
|
+
async handleListReq(req) {
|
|
2391
|
+
const result = await this.tunnelHandler.handleList();
|
|
2392
|
+
return this.wrapResponse(result, req);
|
|
2393
|
+
}
|
|
2394
|
+
wrapResponse(result, req) {
|
|
2395
|
+
if (isErrorResponse(result)) {
|
|
2396
|
+
const errResp = NewErrorResponseObject(result);
|
|
2397
|
+
errResp.command = req.command;
|
|
2398
|
+
errResp.requestid = req.requestid;
|
|
2399
|
+
return errResp;
|
|
2400
|
+
}
|
|
2401
|
+
const finalResult = JSON.parse(JSON.stringify(result));
|
|
2402
|
+
if (Array.isArray(finalResult)) {
|
|
2403
|
+
finalResult.forEach((item) => {
|
|
2404
|
+
if (item?.tunnelconfig) {
|
|
2405
|
+
delete item.tunnelconfig.allowPreflight;
|
|
2406
|
+
}
|
|
2407
|
+
});
|
|
2408
|
+
} else if (finalResult?.tunnelconfig) {
|
|
2409
|
+
delete finalResult.tunnelconfig.allowPreflight;
|
|
2410
|
+
}
|
|
2411
|
+
const respObj = NewResponseObject(finalResult);
|
|
2412
|
+
respObj.command = req.command;
|
|
2413
|
+
respObj.requestid = req.requestid;
|
|
2414
|
+
return respObj;
|
|
2415
|
+
}
|
|
2416
|
+
async handle(ws, req) {
|
|
2417
|
+
const cmd = (req.command || "").toLowerCase();
|
|
2418
|
+
const raw = this.safeParse(req.data);
|
|
2419
|
+
try {
|
|
2420
|
+
let response;
|
|
2421
|
+
switch (cmd) {
|
|
2422
|
+
case "start": {
|
|
2423
|
+
response = await this.handleStartReq(req, raw);
|
|
2424
|
+
break;
|
|
2425
|
+
}
|
|
2426
|
+
case "stop": {
|
|
2427
|
+
response = await this.handleStopReq(req, raw);
|
|
2428
|
+
break;
|
|
2429
|
+
}
|
|
2430
|
+
case "get": {
|
|
2431
|
+
response = await this.handleGetReq(req, raw);
|
|
2432
|
+
break;
|
|
2433
|
+
}
|
|
2434
|
+
case "restart": {
|
|
2435
|
+
response = await this.handleRestartReq(req, raw);
|
|
2436
|
+
break;
|
|
2437
|
+
}
|
|
2438
|
+
case "updateconfig": {
|
|
2439
|
+
response = await this.handleUpdateConfigReq(req, raw);
|
|
2440
|
+
break;
|
|
2441
|
+
}
|
|
2442
|
+
case "list": {
|
|
2443
|
+
response = await this.handleListReq(req);
|
|
2444
|
+
break;
|
|
2445
|
+
}
|
|
2446
|
+
default:
|
|
2447
|
+
if (typeof req.command === "string") {
|
|
2448
|
+
logger.warn("Unknown command", { command: req.command });
|
|
2449
|
+
}
|
|
2450
|
+
return this.sendError(ws, req, "Invalid command");
|
|
2451
|
+
}
|
|
2452
|
+
logger.debug("Sending response", { command: response.command, requestid: response.requestid });
|
|
2453
|
+
this.sendResponse(ws, response);
|
|
2454
|
+
} catch (e) {
|
|
2455
|
+
if (e instanceof import_zod2.default.ZodError) {
|
|
2456
|
+
logger.warn("Validation failed", { cmd, issues: e.issues });
|
|
2457
|
+
return this.sendError(ws, req, "Invalid request data", ErrorCode.InvalidBodyFormatError);
|
|
2458
|
+
}
|
|
2459
|
+
logger.error("Error handling command", { cmd, error: String(e) });
|
|
2460
|
+
return this.sendError(ws, req, e?.message || "Internal error");
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
};
|
|
2464
|
+
}
|
|
2465
|
+
});
|
|
2375
2466
|
|
|
2376
2467
|
// src/remote_management/remoteManagement.ts
|
|
2377
|
-
var RECONNECT_SLEEP_MS = 5e3;
|
|
2378
|
-
var PING_INTERVAL_MS = 3e4;
|
|
2379
|
-
var _remoteManagementState = {
|
|
2380
|
-
status: "NOT_RUNNING",
|
|
2381
|
-
errorMessage: ""
|
|
2382
|
-
};
|
|
2383
|
-
var _stopRequested = false;
|
|
2384
|
-
var currentWs = null;
|
|
2385
2468
|
function buildRemoteManagementWsUrl(manage) {
|
|
2386
2469
|
let baseUrl = (manage || "dashboard.pinggy.io").trim();
|
|
2387
2470
|
if (!(baseUrl.startsWith("ws://") || baseUrl.startsWith("wss://"))) {
|
|
@@ -2541,15 +2624,33 @@ function setRemoteManagementState(state, errorMessage) {
|
|
|
2541
2624
|
errorMessage: errorMessage || ""
|
|
2542
2625
|
};
|
|
2543
2626
|
}
|
|
2627
|
+
var import_ws, RECONNECT_SLEEP_MS, PING_INTERVAL_MS, _remoteManagementState, _stopRequested, currentWs;
|
|
2628
|
+
var init_remoteManagement = __esm({
|
|
2629
|
+
"src/remote_management/remoteManagement.ts"() {
|
|
2630
|
+
"use strict";
|
|
2631
|
+
init_cjs_shims();
|
|
2632
|
+
import_ws = __toESM(require("ws"), 1);
|
|
2633
|
+
init_logger();
|
|
2634
|
+
init_websocket_handlers();
|
|
2635
|
+
init_printer();
|
|
2636
|
+
init_types();
|
|
2637
|
+
RECONNECT_SLEEP_MS = 5e3;
|
|
2638
|
+
PING_INTERVAL_MS = 3e4;
|
|
2639
|
+
_remoteManagementState = {
|
|
2640
|
+
status: "NOT_RUNNING",
|
|
2641
|
+
errorMessage: ""
|
|
2642
|
+
};
|
|
2643
|
+
_stopRequested = false;
|
|
2644
|
+
currentWs = null;
|
|
2645
|
+
}
|
|
2646
|
+
});
|
|
2544
2647
|
|
|
2545
2648
|
// src/utils/parseArgs.ts
|
|
2546
|
-
var import_util3 = require("util");
|
|
2547
|
-
var os = __toESM(require("os"), 1);
|
|
2548
2649
|
function isInlineColonFlag(arg) {
|
|
2549
2650
|
return /^-([RL])[A-Za-z0-9._-]*:?$/.test(arg);
|
|
2550
2651
|
}
|
|
2551
2652
|
function preprocessWindowsArgs(args) {
|
|
2552
|
-
if (
|
|
2653
|
+
if (os2.platform() !== "win32") return args;
|
|
2553
2654
|
const out = [];
|
|
2554
2655
|
let i = 0;
|
|
2555
2656
|
while (i < args.length) {
|
|
@@ -2573,7 +2674,7 @@ function preprocessWindowsArgs(args) {
|
|
|
2573
2674
|
function parseCliArgs(options) {
|
|
2574
2675
|
const rawArgs = process.argv.slice(2);
|
|
2575
2676
|
const processedArgs = preprocessWindowsArgs(rawArgs);
|
|
2576
|
-
const parsed = (0,
|
|
2677
|
+
const parsed = (0, import_util4.parseArgs)({
|
|
2577
2678
|
args: processedArgs,
|
|
2578
2679
|
options,
|
|
2579
2680
|
allowPositionals: true
|
|
@@ -2584,9 +2685,17 @@ function parseCliArgs(options) {
|
|
|
2584
2685
|
hasAnyArgs
|
|
2585
2686
|
};
|
|
2586
2687
|
}
|
|
2688
|
+
var import_util4, os2;
|
|
2689
|
+
var init_parseArgs = __esm({
|
|
2690
|
+
"src/utils/parseArgs.ts"() {
|
|
2691
|
+
"use strict";
|
|
2692
|
+
init_cjs_shims();
|
|
2693
|
+
import_util4 = require("util");
|
|
2694
|
+
os2 = __toESM(require("os"), 1);
|
|
2695
|
+
}
|
|
2696
|
+
});
|
|
2587
2697
|
|
|
2588
2698
|
// src/utils/getFreePort.ts
|
|
2589
|
-
var import_net2 = __toESM(require("net"), 1);
|
|
2590
2699
|
function getFreePort(webDebugger) {
|
|
2591
2700
|
return new Promise((resolve, reject) => {
|
|
2592
2701
|
const tryPort = (portToTry) => {
|
|
@@ -2616,15 +2725,16 @@ function getFreePort(webDebugger) {
|
|
|
2616
2725
|
tryPort(providedPort);
|
|
2617
2726
|
});
|
|
2618
2727
|
}
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2728
|
+
var import_net2;
|
|
2729
|
+
var init_getFreePort = __esm({
|
|
2730
|
+
"src/utils/getFreePort.ts"() {
|
|
2731
|
+
"use strict";
|
|
2732
|
+
init_cjs_shims();
|
|
2733
|
+
import_net2 = __toESM(require("net"), 1);
|
|
2734
|
+
}
|
|
2735
|
+
});
|
|
2625
2736
|
|
|
2626
2737
|
// src/tui/blessed/qrCodeGenerator.ts
|
|
2627
|
-
var import_qrcode = __toESM(require("qrcode"), 1);
|
|
2628
2738
|
async function createQrCodes(urls) {
|
|
2629
2739
|
const codes = [];
|
|
2630
2740
|
for (const url of urls) {
|
|
@@ -2638,17 +2748,16 @@ async function createQrCodes(urls) {
|
|
|
2638
2748
|
}
|
|
2639
2749
|
return codes;
|
|
2640
2750
|
}
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2751
|
+
var import_qrcode;
|
|
2752
|
+
var init_qrCodeGenerator = __esm({
|
|
2753
|
+
"src/tui/blessed/qrCodeGenerator.ts"() {
|
|
2754
|
+
"use strict";
|
|
2755
|
+
init_cjs_shims();
|
|
2756
|
+
import_qrcode = __toESM(require("qrcode"), 1);
|
|
2757
|
+
}
|
|
2758
|
+
});
|
|
2644
2759
|
|
|
2645
2760
|
// src/tui/blessed/config.ts
|
|
2646
|
-
var defaultTuiConfig = {
|
|
2647
|
-
maxRequestPairs: 100,
|
|
2648
|
-
visibleRequestCount: 10,
|
|
2649
|
-
viewportScrollMargin: 2,
|
|
2650
|
-
inactivityHttpSelectorTimeoutMs: 1e4
|
|
2651
|
-
};
|
|
2652
2761
|
function getTuiConfig() {
|
|
2653
2762
|
return {
|
|
2654
2763
|
maxRequestPairs: defaultTuiConfig.maxRequestPairs,
|
|
@@ -2657,6 +2766,19 @@ function getTuiConfig() {
|
|
|
2657
2766
|
inactivityHttpSelectorTimeoutMs: defaultTuiConfig.inactivityHttpSelectorTimeoutMs
|
|
2658
2767
|
};
|
|
2659
2768
|
}
|
|
2769
|
+
var defaultTuiConfig;
|
|
2770
|
+
var init_config = __esm({
|
|
2771
|
+
"src/tui/blessed/config.ts"() {
|
|
2772
|
+
"use strict";
|
|
2773
|
+
init_cjs_shims();
|
|
2774
|
+
defaultTuiConfig = {
|
|
2775
|
+
maxRequestPairs: 100,
|
|
2776
|
+
visibleRequestCount: 10,
|
|
2777
|
+
viewportScrollMargin: 2,
|
|
2778
|
+
inactivityHttpSelectorTimeoutMs: 1e4
|
|
2779
|
+
};
|
|
2780
|
+
}
|
|
2781
|
+
});
|
|
2660
2782
|
|
|
2661
2783
|
// src/tui/blessed/webDebuggerConnection.ts
|
|
2662
2784
|
function createWebDebuggerConnection(webDebuggerUrl, onUpdate) {
|
|
@@ -2750,22 +2872,34 @@ function createWebDebuggerConnection(webDebuggerUrl, onUpdate) {
|
|
|
2750
2872
|
}
|
|
2751
2873
|
};
|
|
2752
2874
|
}
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2875
|
+
var import_ws2;
|
|
2876
|
+
var init_webDebuggerConnection = __esm({
|
|
2877
|
+
"src/tui/blessed/webDebuggerConnection.ts"() {
|
|
2878
|
+
"use strict";
|
|
2879
|
+
init_cjs_shims();
|
|
2880
|
+
import_ws2 = __toESM(require("ws"), 1);
|
|
2881
|
+
init_logger();
|
|
2882
|
+
init_config();
|
|
2883
|
+
}
|
|
2884
|
+
});
|
|
2756
2885
|
|
|
2757
2886
|
// src/tui/ink/asciArt.ts
|
|
2758
|
-
var asciiArtPinggyLogo
|
|
2887
|
+
var asciiArtPinggyLogo;
|
|
2888
|
+
var init_asciArt = __esm({
|
|
2889
|
+
"src/tui/ink/asciArt.ts"() {
|
|
2890
|
+
"use strict";
|
|
2891
|
+
init_cjs_shims();
|
|
2892
|
+
asciiArtPinggyLogo = `
|
|
2759
2893
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557
|
|
2760
2894
|
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D
|
|
2761
2895
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2554\u255D
|
|
2762
2896
|
\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2554\u255D
|
|
2763
2897
|
\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551
|
|
2764
2898
|
\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D `;
|
|
2899
|
+
}
|
|
2900
|
+
});
|
|
2765
2901
|
|
|
2766
2902
|
// src/tui/blessed/components/UIComponents.ts
|
|
2767
|
-
var MIN_WIDTH_WARNING = 60;
|
|
2768
|
-
var SIMPLE_LAYOUT_THRESHOLD = 80;
|
|
2769
2903
|
function colorizeGradient(text) {
|
|
2770
2904
|
const colors = ["red", "yellow", "green", "cyan", "blue", "magenta"];
|
|
2771
2905
|
const lines = text.split("\n");
|
|
@@ -2987,6 +3121,17 @@ function createSimpleUI(screen, urls, greet) {
|
|
|
2987
3121
|
footerBox
|
|
2988
3122
|
};
|
|
2989
3123
|
}
|
|
3124
|
+
var import_blessed, MIN_WIDTH_WARNING, SIMPLE_LAYOUT_THRESHOLD;
|
|
3125
|
+
var init_UIComponents = __esm({
|
|
3126
|
+
"src/tui/blessed/components/UIComponents.ts"() {
|
|
3127
|
+
"use strict";
|
|
3128
|
+
init_cjs_shims();
|
|
3129
|
+
import_blessed = __toESM(require("blessed"), 1);
|
|
3130
|
+
init_asciArt();
|
|
3131
|
+
MIN_WIDTH_WARNING = 60;
|
|
3132
|
+
SIMPLE_LAYOUT_THRESHOLD = 80;
|
|
3133
|
+
}
|
|
3134
|
+
});
|
|
2990
3135
|
|
|
2991
3136
|
// src/tui/ink/utils/utils.ts
|
|
2992
3137
|
function getStatusColor(status) {
|
|
@@ -3019,6 +3164,12 @@ function getBytesInt(b) {
|
|
|
3019
3164
|
}
|
|
3020
3165
|
return `${b.toFixed(2)} `;
|
|
3021
3166
|
}
|
|
3167
|
+
var init_utils = __esm({
|
|
3168
|
+
"src/tui/ink/utils/utils.ts"() {
|
|
3169
|
+
"use strict";
|
|
3170
|
+
init_cjs_shims();
|
|
3171
|
+
}
|
|
3172
|
+
});
|
|
3022
3173
|
|
|
3023
3174
|
// src/tui/blessed/components/DisplayUpdaters.ts
|
|
3024
3175
|
function updateUrlsDisplay(urlsBox, screen, urls, currentQrIndex) {
|
|
@@ -3135,9 +3286,16 @@ function updateQrCodeDisplay(qrCodeBox, screen, qrCodes, urls, currentQrIndex) {
|
|
|
3135
3286
|
qrCodeBox.parseContent();
|
|
3136
3287
|
screen.render();
|
|
3137
3288
|
}
|
|
3289
|
+
var init_DisplayUpdaters = __esm({
|
|
3290
|
+
"src/tui/blessed/components/DisplayUpdaters.ts"() {
|
|
3291
|
+
"use strict";
|
|
3292
|
+
init_cjs_shims();
|
|
3293
|
+
init_utils();
|
|
3294
|
+
init_config();
|
|
3295
|
+
}
|
|
3296
|
+
});
|
|
3138
3297
|
|
|
3139
3298
|
// src/tui/blessed/components/Modals.ts
|
|
3140
|
-
var import_blessed2 = __toESM(require("blessed"), 1);
|
|
3141
3299
|
function showDetailModal(screen, manager, requestText, responseText) {
|
|
3142
3300
|
manager.inDetailView = true;
|
|
3143
3301
|
manager.detailModal = import_blessed2.default.box({
|
|
@@ -3340,6 +3498,14 @@ function showErrorModal(screen, modalManager, title = "Error", message) {
|
|
|
3340
3498
|
modalManager.loadingView = true;
|
|
3341
3499
|
screen.render();
|
|
3342
3500
|
}
|
|
3501
|
+
var import_blessed2;
|
|
3502
|
+
var init_Modals = __esm({
|
|
3503
|
+
"src/tui/blessed/components/Modals.ts"() {
|
|
3504
|
+
"use strict";
|
|
3505
|
+
init_cjs_shims();
|
|
3506
|
+
import_blessed2 = __toESM(require("blessed"), 1);
|
|
3507
|
+
}
|
|
3508
|
+
});
|
|
3343
3509
|
|
|
3344
3510
|
// src/tui/blessed/headerFetcher.ts
|
|
3345
3511
|
async function fetchReqResHeaders(baseUrl, key, signal) {
|
|
@@ -3367,6 +3533,13 @@ async function fetchReqResHeaders(baseUrl, key, signal) {
|
|
|
3367
3533
|
throw err;
|
|
3368
3534
|
}
|
|
3369
3535
|
}
|
|
3536
|
+
var init_headerFetcher = __esm({
|
|
3537
|
+
"src/tui/blessed/headerFetcher.ts"() {
|
|
3538
|
+
"use strict";
|
|
3539
|
+
init_cjs_shims();
|
|
3540
|
+
init_logger();
|
|
3541
|
+
}
|
|
3542
|
+
});
|
|
3370
3543
|
|
|
3371
3544
|
// src/tui/blessed/components/KeyBindings.ts
|
|
3372
3545
|
function setupKeyBindings(screen, modalManager, state, callbacks, tunnelConfig) {
|
|
@@ -3530,264 +3703,291 @@ function setupKeyBindings(screen, modalManager, state, callbacks, tunnelConfig)
|
|
|
3530
3703
|
}
|
|
3531
3704
|
});
|
|
3532
3705
|
}
|
|
3706
|
+
var init_KeyBindings = __esm({
|
|
3707
|
+
"src/tui/blessed/components/KeyBindings.ts"() {
|
|
3708
|
+
"use strict";
|
|
3709
|
+
init_cjs_shims();
|
|
3710
|
+
init_headerFetcher();
|
|
3711
|
+
init_logger();
|
|
3712
|
+
init_Modals();
|
|
3713
|
+
init_config();
|
|
3714
|
+
}
|
|
3715
|
+
});
|
|
3533
3716
|
|
|
3534
3717
|
// src/tui/blessed/TunnelTui.ts
|
|
3535
|
-
var TunnelTui
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
(
|
|
3602
|
-
this.
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3718
|
+
var import_blessed3, TunnelTui;
|
|
3719
|
+
var init_TunnelTui = __esm({
|
|
3720
|
+
"src/tui/blessed/TunnelTui.ts"() {
|
|
3721
|
+
"use strict";
|
|
3722
|
+
init_cjs_shims();
|
|
3723
|
+
import_blessed3 = __toESM(require("blessed"), 1);
|
|
3724
|
+
init_qrCodeGenerator();
|
|
3725
|
+
init_webDebuggerConnection();
|
|
3726
|
+
init_TunnelManager();
|
|
3727
|
+
init_UIComponents();
|
|
3728
|
+
init_DisplayUpdaters();
|
|
3729
|
+
init_Modals();
|
|
3730
|
+
init_KeyBindings();
|
|
3731
|
+
TunnelTui = class {
|
|
3732
|
+
constructor(props) {
|
|
3733
|
+
// State
|
|
3734
|
+
this.currentQrIndex = 0;
|
|
3735
|
+
this.selectedIndex = -1;
|
|
3736
|
+
// -1 means no selection
|
|
3737
|
+
this.selectedRequestKey = null;
|
|
3738
|
+
// Track selected request by key
|
|
3739
|
+
this.qrCodes = [];
|
|
3740
|
+
this.stats = {
|
|
3741
|
+
elapsedTime: 0,
|
|
3742
|
+
numLiveConnections: 0,
|
|
3743
|
+
numTotalConnections: 0,
|
|
3744
|
+
numTotalReqBytes: 0,
|
|
3745
|
+
numTotalResBytes: 0,
|
|
3746
|
+
numTotalTxBytes: 0
|
|
3747
|
+
};
|
|
3748
|
+
this.pairs = [];
|
|
3749
|
+
this.webDebuggerConnection = null;
|
|
3750
|
+
this.modalManager = {
|
|
3751
|
+
detailModal: null,
|
|
3752
|
+
keyBindingsModal: null,
|
|
3753
|
+
disconnectModal: null,
|
|
3754
|
+
inDetailView: false,
|
|
3755
|
+
keyBindingView: false,
|
|
3756
|
+
inDisconnectView: false,
|
|
3757
|
+
loadingBox: null,
|
|
3758
|
+
loadingView: false,
|
|
3759
|
+
fetchAbortController: null
|
|
3760
|
+
};
|
|
3761
|
+
this.exitPromiseResolve = null;
|
|
3762
|
+
this.urls = props.urls;
|
|
3763
|
+
this.greet = props.greet || "";
|
|
3764
|
+
this.tunnelConfig = props.tunnelConfig;
|
|
3765
|
+
this.disconnectInfo = props.disconnectInfo;
|
|
3766
|
+
if (props.tunnelInstance) {
|
|
3767
|
+
this.tunnelInstance = props.tunnelInstance;
|
|
3768
|
+
}
|
|
3769
|
+
this.exitPromise = new Promise((resolve) => {
|
|
3770
|
+
this.exitPromiseResolve = resolve;
|
|
3771
|
+
});
|
|
3772
|
+
this.screen = import_blessed3.default.screen({
|
|
3773
|
+
smartCSR: true,
|
|
3774
|
+
title: "Pinggy Tunnel",
|
|
3775
|
+
fullUnicode: true
|
|
3776
|
+
});
|
|
3777
|
+
this.setupStatsListener();
|
|
3778
|
+
this.setupWebDebugger();
|
|
3779
|
+
this.generateQrCodes();
|
|
3780
|
+
this.createUI();
|
|
3781
|
+
this.setupKeyBindings();
|
|
3782
|
+
}
|
|
3783
|
+
setupStatsListener() {
|
|
3784
|
+
globalThis.__PINGGY_TUNNEL_STATS__ = (newStats2) => {
|
|
3785
|
+
this.stats = { ...newStats2 };
|
|
3786
|
+
this.updateStatsDisplay();
|
|
3787
|
+
};
|
|
3788
|
+
}
|
|
3789
|
+
clearSelection() {
|
|
3790
|
+
this.selectedIndex = -1;
|
|
3791
|
+
this.selectedRequestKey = null;
|
|
3792
|
+
}
|
|
3793
|
+
setupWebDebugger() {
|
|
3794
|
+
if (this.tunnelConfig?.webDebugger) {
|
|
3795
|
+
this.webDebuggerConnection = createWebDebuggerConnection(
|
|
3796
|
+
this.tunnelConfig.webDebugger,
|
|
3797
|
+
(pairs) => {
|
|
3798
|
+
this.pairs = pairs;
|
|
3799
|
+
if (this.selectedRequestKey !== null) {
|
|
3800
|
+
const newIndex = pairs.findIndex(
|
|
3801
|
+
(pair) => pair.request?.key === this.selectedRequestKey
|
|
3802
|
+
);
|
|
3803
|
+
if (newIndex !== -1) {
|
|
3804
|
+
this.selectedIndex = newIndex;
|
|
3805
|
+
} else {
|
|
3806
|
+
this.clearSelection();
|
|
3807
|
+
}
|
|
3808
|
+
}
|
|
3809
|
+
this.updateRequestsDisplay();
|
|
3611
3810
|
}
|
|
3811
|
+
);
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
async generateQrCodes() {
|
|
3815
|
+
if (this.tunnelConfig?.qrCode && this.urls.length > 0) {
|
|
3816
|
+
this.qrCodes = await createQrCodes(this.urls);
|
|
3817
|
+
this.updateQrCodeDisplay();
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
// Create the UI based on terminal size
|
|
3821
|
+
createUI() {
|
|
3822
|
+
this.buildUI();
|
|
3823
|
+
this.screen.on("resize", () => {
|
|
3824
|
+
this.handleResize();
|
|
3825
|
+
});
|
|
3826
|
+
}
|
|
3827
|
+
buildUI() {
|
|
3828
|
+
const width = this.screen.width;
|
|
3829
|
+
if (width < MIN_WIDTH_WARNING) {
|
|
3830
|
+
this.uiElements = {
|
|
3831
|
+
mainContainer: createWarningUI(this.screen),
|
|
3832
|
+
urlsBox: null,
|
|
3833
|
+
statsBox: null,
|
|
3834
|
+
requestsBox: null,
|
|
3835
|
+
footerBox: null,
|
|
3836
|
+
warningBox: createWarningUI(this.screen)
|
|
3837
|
+
};
|
|
3838
|
+
this.screen.render();
|
|
3839
|
+
return;
|
|
3840
|
+
}
|
|
3841
|
+
if (width < SIMPLE_LAYOUT_THRESHOLD) {
|
|
3842
|
+
this.uiElements = createSimpleUI(this.screen, this.urls, this.greet);
|
|
3843
|
+
} else {
|
|
3844
|
+
this.uiElements = createFullUI(this.screen, this.urls, this.greet, this.tunnelConfig);
|
|
3845
|
+
}
|
|
3846
|
+
this.refreshDisplays();
|
|
3847
|
+
this.screen.render();
|
|
3848
|
+
}
|
|
3849
|
+
refreshDisplays() {
|
|
3850
|
+
this.updateUrlsDisplay();
|
|
3851
|
+
this.updateStatsDisplay();
|
|
3852
|
+
this.updateRequestsDisplay();
|
|
3853
|
+
this.updateQrCodeDisplay();
|
|
3854
|
+
}
|
|
3855
|
+
updateUrlsDisplay() {
|
|
3856
|
+
updateUrlsDisplay(
|
|
3857
|
+
this.uiElements?.urlsBox,
|
|
3858
|
+
this.screen,
|
|
3859
|
+
this.urls,
|
|
3860
|
+
this.currentQrIndex
|
|
3861
|
+
);
|
|
3862
|
+
}
|
|
3863
|
+
updateStatsDisplay() {
|
|
3864
|
+
updateStatsDisplay(
|
|
3865
|
+
this.uiElements?.statsBox,
|
|
3866
|
+
this.screen,
|
|
3867
|
+
this.stats
|
|
3868
|
+
);
|
|
3869
|
+
}
|
|
3870
|
+
updateRequestsDisplay() {
|
|
3871
|
+
const result = updateRequestsDisplay(
|
|
3872
|
+
this.uiElements?.requestsBox,
|
|
3873
|
+
this.screen,
|
|
3874
|
+
this.pairs,
|
|
3875
|
+
this.selectedIndex
|
|
3876
|
+
);
|
|
3877
|
+
if (result.adjustedSelectedIndex !== this.selectedIndex) {
|
|
3878
|
+
if (result.adjustedSelectedIndex === -1) {
|
|
3879
|
+
this.clearSelection();
|
|
3880
|
+
} else {
|
|
3881
|
+
this.selectedIndex = result.adjustedSelectedIndex;
|
|
3612
3882
|
}
|
|
3613
|
-
this.updateRequestsDisplay();
|
|
3614
3883
|
}
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
async generateQrCodes() {
|
|
3619
|
-
if (this.tunnelConfig?.qrCode && this.urls.length > 0) {
|
|
3620
|
-
this.qrCodes = await createQrCodes(this.urls);
|
|
3621
|
-
this.updateQrCodeDisplay();
|
|
3622
|
-
}
|
|
3623
|
-
}
|
|
3624
|
-
// Create the UI based on terminal size
|
|
3625
|
-
createUI() {
|
|
3626
|
-
this.buildUI();
|
|
3627
|
-
this.screen.on("resize", () => {
|
|
3628
|
-
this.handleResize();
|
|
3629
|
-
});
|
|
3630
|
-
}
|
|
3631
|
-
buildUI() {
|
|
3632
|
-
const width = this.screen.width;
|
|
3633
|
-
if (width < MIN_WIDTH_WARNING) {
|
|
3634
|
-
this.uiElements = {
|
|
3635
|
-
mainContainer: createWarningUI(this.screen),
|
|
3636
|
-
urlsBox: null,
|
|
3637
|
-
statsBox: null,
|
|
3638
|
-
requestsBox: null,
|
|
3639
|
-
footerBox: null,
|
|
3640
|
-
warningBox: createWarningUI(this.screen)
|
|
3641
|
-
};
|
|
3642
|
-
this.screen.render();
|
|
3643
|
-
return;
|
|
3644
|
-
}
|
|
3645
|
-
if (width < SIMPLE_LAYOUT_THRESHOLD) {
|
|
3646
|
-
this.uiElements = createSimpleUI(this.screen, this.urls, this.greet);
|
|
3647
|
-
} else {
|
|
3648
|
-
this.uiElements = createFullUI(this.screen, this.urls, this.greet, this.tunnelConfig);
|
|
3649
|
-
}
|
|
3650
|
-
this.refreshDisplays();
|
|
3651
|
-
this.screen.render();
|
|
3652
|
-
}
|
|
3653
|
-
refreshDisplays() {
|
|
3654
|
-
this.updateUrlsDisplay();
|
|
3655
|
-
this.updateStatsDisplay();
|
|
3656
|
-
this.updateRequestsDisplay();
|
|
3657
|
-
this.updateQrCodeDisplay();
|
|
3658
|
-
}
|
|
3659
|
-
updateUrlsDisplay() {
|
|
3660
|
-
updateUrlsDisplay(
|
|
3661
|
-
this.uiElements?.urlsBox,
|
|
3662
|
-
this.screen,
|
|
3663
|
-
this.urls,
|
|
3664
|
-
this.currentQrIndex
|
|
3665
|
-
);
|
|
3666
|
-
}
|
|
3667
|
-
updateStatsDisplay() {
|
|
3668
|
-
updateStatsDisplay(
|
|
3669
|
-
this.uiElements?.statsBox,
|
|
3670
|
-
this.screen,
|
|
3671
|
-
this.stats
|
|
3672
|
-
);
|
|
3673
|
-
}
|
|
3674
|
-
updateRequestsDisplay() {
|
|
3675
|
-
const result = updateRequestsDisplay(
|
|
3676
|
-
this.uiElements?.requestsBox,
|
|
3677
|
-
this.screen,
|
|
3678
|
-
this.pairs,
|
|
3679
|
-
this.selectedIndex
|
|
3680
|
-
);
|
|
3681
|
-
if (result.adjustedSelectedIndex !== this.selectedIndex) {
|
|
3682
|
-
if (result.adjustedSelectedIndex === -1) {
|
|
3683
|
-
this.clearSelection();
|
|
3684
|
-
} else {
|
|
3685
|
-
this.selectedIndex = result.adjustedSelectedIndex;
|
|
3884
|
+
if (result.trimmedPairs !== this.pairs) {
|
|
3885
|
+
this.pairs = result.trimmedPairs;
|
|
3886
|
+
}
|
|
3686
3887
|
}
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
this.screen,
|
|
3696
|
-
this.qrCodes,
|
|
3697
|
-
this.urls,
|
|
3698
|
-
this.currentQrIndex
|
|
3699
|
-
);
|
|
3700
|
-
}
|
|
3701
|
-
setupKeyBindings() {
|
|
3702
|
-
const self = this;
|
|
3703
|
-
const state = {
|
|
3704
|
-
get currentQrIndex() {
|
|
3705
|
-
return self.currentQrIndex;
|
|
3706
|
-
},
|
|
3707
|
-
set currentQrIndex(value) {
|
|
3708
|
-
self.currentQrIndex = value;
|
|
3709
|
-
},
|
|
3710
|
-
get selectedIndex() {
|
|
3711
|
-
return self.selectedIndex;
|
|
3712
|
-
},
|
|
3713
|
-
set selectedIndex(value) {
|
|
3714
|
-
self.selectedIndex = value;
|
|
3715
|
-
},
|
|
3716
|
-
get pairs() {
|
|
3717
|
-
return self.pairs;
|
|
3718
|
-
},
|
|
3719
|
-
get urls() {
|
|
3720
|
-
return self.urls;
|
|
3888
|
+
updateQrCodeDisplay() {
|
|
3889
|
+
updateQrCodeDisplay(
|
|
3890
|
+
this.uiElements?.qrCodeBox,
|
|
3891
|
+
this.screen,
|
|
3892
|
+
this.qrCodes,
|
|
3893
|
+
this.urls,
|
|
3894
|
+
this.currentQrIndex
|
|
3895
|
+
);
|
|
3721
3896
|
}
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3897
|
+
setupKeyBindings() {
|
|
3898
|
+
const self = this;
|
|
3899
|
+
const state = {
|
|
3900
|
+
get currentQrIndex() {
|
|
3901
|
+
return self.currentQrIndex;
|
|
3902
|
+
},
|
|
3903
|
+
set currentQrIndex(value) {
|
|
3904
|
+
self.currentQrIndex = value;
|
|
3905
|
+
},
|
|
3906
|
+
get selectedIndex() {
|
|
3907
|
+
return self.selectedIndex;
|
|
3908
|
+
},
|
|
3909
|
+
set selectedIndex(value) {
|
|
3910
|
+
self.selectedIndex = value;
|
|
3911
|
+
},
|
|
3912
|
+
get pairs() {
|
|
3913
|
+
return self.pairs;
|
|
3914
|
+
},
|
|
3915
|
+
get urls() {
|
|
3916
|
+
return self.urls;
|
|
3917
|
+
}
|
|
3918
|
+
};
|
|
3919
|
+
const callbacks = {
|
|
3920
|
+
onQrIndexChange: (index) => {
|
|
3921
|
+
self.currentQrIndex = index;
|
|
3922
|
+
},
|
|
3923
|
+
onSelectedIndexChange: (index, requestKey) => {
|
|
3924
|
+
self.selectedIndex = index;
|
|
3925
|
+
self.selectedRequestKey = requestKey;
|
|
3926
|
+
},
|
|
3927
|
+
onDestroy: () => self.destroy(),
|
|
3928
|
+
updateUrlsDisplay: () => self.updateUrlsDisplay(),
|
|
3929
|
+
updateQrCodeDisplay: () => self.updateQrCodeDisplay(),
|
|
3930
|
+
updateRequestsDisplay: () => self.updateRequestsDisplay()
|
|
3931
|
+
};
|
|
3932
|
+
setupKeyBindings(
|
|
3933
|
+
this.screen,
|
|
3934
|
+
this.modalManager,
|
|
3935
|
+
state,
|
|
3936
|
+
callbacks,
|
|
3937
|
+
this.tunnelConfig
|
|
3938
|
+
);
|
|
3939
|
+
}
|
|
3940
|
+
handleResize() {
|
|
3941
|
+
this.screen.children.forEach((child) => child.destroy());
|
|
3942
|
+
this.buildUI();
|
|
3943
|
+
}
|
|
3944
|
+
updateDisconnectInfo(info) {
|
|
3945
|
+
this.disconnectInfo = info;
|
|
3946
|
+
if (info?.disconnected) {
|
|
3947
|
+
const message = info.error ? `Error: ${info.error}
|
|
3752
3948
|
Tunnel will be closed.` : info.messages?.join("\n") || "Disconnect request received. Tunnel will be closed.";
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3949
|
+
showDisconnectModal(
|
|
3950
|
+
this.screen,
|
|
3951
|
+
this.modalManager,
|
|
3952
|
+
message,
|
|
3953
|
+
() => this.destroy()
|
|
3954
|
+
);
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3957
|
+
start() {
|
|
3958
|
+
this.screen.render();
|
|
3959
|
+
}
|
|
3960
|
+
waitUntilExit() {
|
|
3961
|
+
return this.exitPromise;
|
|
3962
|
+
}
|
|
3963
|
+
destroy() {
|
|
3964
|
+
if (this.tunnelInstance?.tunnelid) {
|
|
3965
|
+
const manager = TunnelManager.getInstance();
|
|
3966
|
+
manager.stopTunnel(this.tunnelInstance.tunnelid);
|
|
3967
|
+
}
|
|
3968
|
+
delete globalThis.__PINGGY_TUNNEL_STATS__;
|
|
3969
|
+
if (this.webDebuggerConnection) {
|
|
3970
|
+
this.webDebuggerConnection.close();
|
|
3971
|
+
}
|
|
3972
|
+
this.screen.destroy();
|
|
3973
|
+
if (this.exitPromiseResolve) {
|
|
3974
|
+
this.exitPromiseResolve();
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
};
|
|
3766
3978
|
}
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
}
|
|
3776
|
-
this.screen.destroy();
|
|
3777
|
-
if (this.exitPromiseResolve) {
|
|
3778
|
-
this.exitPromiseResolve();
|
|
3779
|
-
}
|
|
3979
|
+
});
|
|
3980
|
+
|
|
3981
|
+
// src/tui/blessed/index.ts
|
|
3982
|
+
var init_blessed = __esm({
|
|
3983
|
+
"src/tui/blessed/index.ts"() {
|
|
3984
|
+
"use strict";
|
|
3985
|
+
init_cjs_shims();
|
|
3986
|
+
init_TunnelTui();
|
|
3780
3987
|
}
|
|
3781
|
-
};
|
|
3988
|
+
});
|
|
3782
3989
|
|
|
3783
3990
|
// src/cli/starCli.ts
|
|
3784
|
-
var TunnelData = {
|
|
3785
|
-
urls: null,
|
|
3786
|
-
greet: null,
|
|
3787
|
-
usage: null
|
|
3788
|
-
};
|
|
3789
|
-
var activeTui = null;
|
|
3790
|
-
var disconnectState = null;
|
|
3791
3991
|
async function launchTui(finalConfig, urls, greet, tunnel) {
|
|
3792
3992
|
try {
|
|
3793
3993
|
const isTTYEnabled = process.stdin.isTTY;
|
|
@@ -3930,11 +4130,37 @@ async function startCli(finalConfig, manager) {
|
|
|
3930
4130
|
throw err;
|
|
3931
4131
|
}
|
|
3932
4132
|
}
|
|
4133
|
+
var import_picocolors3, TunnelData, activeTui, disconnectState;
|
|
4134
|
+
var init_starCli = __esm({
|
|
4135
|
+
"src/cli/starCli.ts"() {
|
|
4136
|
+
"use strict";
|
|
4137
|
+
init_cjs_shims();
|
|
4138
|
+
init_printer();
|
|
4139
|
+
init_TunnelManager();
|
|
4140
|
+
init_getFreePort();
|
|
4141
|
+
init_logger();
|
|
4142
|
+
import_picocolors3 = __toESM(require("picocolors"), 1);
|
|
4143
|
+
init_blessed();
|
|
4144
|
+
TunnelData = {
|
|
4145
|
+
urls: null,
|
|
4146
|
+
greet: null,
|
|
4147
|
+
usage: null
|
|
4148
|
+
};
|
|
4149
|
+
activeTui = null;
|
|
4150
|
+
disconnectState = null;
|
|
4151
|
+
}
|
|
4152
|
+
});
|
|
3933
4153
|
|
|
3934
|
-
// src/
|
|
3935
|
-
var
|
|
3936
|
-
|
|
3937
|
-
|
|
4154
|
+
// src/main.ts
|
|
4155
|
+
var main_exports = {};
|
|
4156
|
+
__export(main_exports, {
|
|
4157
|
+
TunnelManager: () => TunnelManager,
|
|
4158
|
+
TunnelOperations: () => TunnelOperations,
|
|
4159
|
+
closeRemoteManagement: () => closeRemoteManagement,
|
|
4160
|
+
enablePackageLogging: () => enablePackageLogging,
|
|
4161
|
+
getRemoteManagementState: () => getRemoteManagementState,
|
|
4162
|
+
initiateRemoteManagement: () => initiateRemoteManagement
|
|
4163
|
+
});
|
|
3938
4164
|
async function main() {
|
|
3939
4165
|
try {
|
|
3940
4166
|
const { values, positionals, hasAnyArgs } = parseCliArgs(cliOptions);
|
|
@@ -3970,22 +4196,178 @@ async function main() {
|
|
|
3970
4196
|
printer_default.error(error);
|
|
3971
4197
|
}
|
|
3972
4198
|
}
|
|
3973
|
-
var
|
|
3974
|
-
var
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
4199
|
+
var import_url, import_process, import_fs4, currentFile, entryFile;
|
|
4200
|
+
var init_main = __esm({
|
|
4201
|
+
"src/main.ts"() {
|
|
4202
|
+
"use strict";
|
|
4203
|
+
init_cjs_shims();
|
|
4204
|
+
init_TunnelManager();
|
|
4205
|
+
init_help();
|
|
4206
|
+
init_options();
|
|
4207
|
+
init_buildConfig();
|
|
4208
|
+
init_logger();
|
|
4209
|
+
init_remoteManagement();
|
|
4210
|
+
init_parseArgs();
|
|
4211
|
+
init_printer();
|
|
4212
|
+
init_starCli();
|
|
4213
|
+
init_util();
|
|
4214
|
+
init_handler();
|
|
4215
|
+
import_url = require("url");
|
|
4216
|
+
import_process = require("process");
|
|
4217
|
+
import_fs4 = require("fs");
|
|
4218
|
+
init_logger();
|
|
4219
|
+
init_remoteManagement();
|
|
4220
|
+
currentFile = (0, import_url.fileURLToPath)(importMetaUrl);
|
|
4221
|
+
entryFile = null;
|
|
4222
|
+
try {
|
|
4223
|
+
entryFile = import_process.argv[1] ? (0, import_fs4.realpathSync)(import_process.argv[1]) : null;
|
|
4224
|
+
} catch (e) {
|
|
4225
|
+
entryFile = null;
|
|
4226
|
+
}
|
|
4227
|
+
if (entryFile && entryFile === currentFile) {
|
|
4228
|
+
main();
|
|
4229
|
+
}
|
|
4230
|
+
}
|
|
4231
|
+
});
|
|
4232
|
+
|
|
4233
|
+
// src/index.ts
|
|
4234
|
+
init_cjs_shims();
|
|
4235
|
+
|
|
4236
|
+
// src/utils/detect_vc_redist_on_windows.ts
|
|
4237
|
+
init_cjs_shims();
|
|
4238
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
4239
|
+
var import_path = __toESM(require("path"), 1);
|
|
4240
|
+
var import_child_process = require("child_process");
|
|
4241
|
+
var import_os = __toESM(require("os"), 1);
|
|
4242
|
+
init_printer();
|
|
4243
|
+
var import_util = require("util");
|
|
4244
|
+
var execAsync = (0, import_util.promisify)(import_child_process.exec);
|
|
4245
|
+
var DLLS = ["vcruntime140.dll", "vcruntime140_1.dll", "msvcp140.dll"];
|
|
4246
|
+
var PATHS = ["C:\\Windows\\System32", "C:\\Windows\\SysWOW64"];
|
|
4247
|
+
var REGISTRY_KEYS = [
|
|
4248
|
+
"HKLM\\SOFTWARE\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\X64",
|
|
4249
|
+
"HKLM\\SOFTWARE\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\X86",
|
|
4250
|
+
"HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\X64",
|
|
4251
|
+
"HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\X86"
|
|
4252
|
+
];
|
|
4253
|
+
function checkDLLs() {
|
|
4254
|
+
return DLLS.every(
|
|
4255
|
+
(dll) => PATHS.some((p) => {
|
|
4256
|
+
try {
|
|
4257
|
+
return import_fs.default.existsSync(import_path.default.join(p, dll));
|
|
4258
|
+
} catch {
|
|
4259
|
+
return false;
|
|
4260
|
+
}
|
|
4261
|
+
})
|
|
4262
|
+
);
|
|
4263
|
+
}
|
|
4264
|
+
function checkRegistry() {
|
|
4265
|
+
if (import_os.default.platform() !== "win32") return false;
|
|
4266
|
+
try {
|
|
4267
|
+
for (const key of REGISTRY_KEYS) {
|
|
4268
|
+
const cmd = `reg query "${key}" /v Installed 2>nul`;
|
|
4269
|
+
const result = (0, import_child_process.execSync)(cmd, { encoding: "utf8" });
|
|
4270
|
+
if (result.includes("0x1")) {
|
|
4271
|
+
return true;
|
|
4272
|
+
}
|
|
4273
|
+
}
|
|
4274
|
+
} catch {
|
|
4275
|
+
}
|
|
4276
|
+
return false;
|
|
4277
|
+
}
|
|
4278
|
+
function getVCRedistVersion() {
|
|
4279
|
+
if (import_os.default.platform() !== "win32") return null;
|
|
4280
|
+
try {
|
|
4281
|
+
for (const key of REGISTRY_KEYS) {
|
|
4282
|
+
const cmd = `reg query "${key}" /v Version 2>nul`;
|
|
4283
|
+
const result = (0, import_child_process.execSync)(cmd, { encoding: "utf8" });
|
|
4284
|
+
const match = result.match(/Version\s+REG_SZ\s+(\S+)/);
|
|
4285
|
+
if (match) {
|
|
4286
|
+
return match[1];
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4289
|
+
} catch {
|
|
4290
|
+
}
|
|
4291
|
+
return null;
|
|
4292
|
+
}
|
|
4293
|
+
function hasVCRedist() {
|
|
4294
|
+
if (import_os.default.platform() !== "win32") {
|
|
4295
|
+
return true;
|
|
4296
|
+
}
|
|
4297
|
+
if (checkRegistry()) {
|
|
4298
|
+
return true;
|
|
4299
|
+
}
|
|
4300
|
+
if (checkDLLs()) {
|
|
4301
|
+
return true;
|
|
4302
|
+
}
|
|
4303
|
+
return false;
|
|
4304
|
+
}
|
|
4305
|
+
function getVCRedistStatus() {
|
|
4306
|
+
if (import_os.default.platform() !== "win32") {
|
|
4307
|
+
return {
|
|
4308
|
+
required: false,
|
|
4309
|
+
installed: true,
|
|
4310
|
+
version: null,
|
|
4311
|
+
method: "non-windows"
|
|
4312
|
+
};
|
|
4313
|
+
}
|
|
4314
|
+
const registryInstalled = checkRegistry();
|
|
4315
|
+
const dllsPresent = checkDLLs();
|
|
4316
|
+
const version = getVCRedistVersion();
|
|
4317
|
+
return {
|
|
4318
|
+
required: true,
|
|
4319
|
+
installed: registryInstalled || dllsPresent,
|
|
4320
|
+
version,
|
|
4321
|
+
registryCheck: registryInstalled,
|
|
4322
|
+
dllCheck: dllsPresent,
|
|
4323
|
+
method: registryInstalled ? "registry" : dllsPresent ? "dll" : "none"
|
|
4324
|
+
};
|
|
4325
|
+
}
|
|
4326
|
+
async function openDownloadPage() {
|
|
4327
|
+
if (process.platform !== "win32") {
|
|
4328
|
+
return;
|
|
4329
|
+
}
|
|
4330
|
+
const url = "https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170";
|
|
4331
|
+
const command = `cmd.exe /c start "" "${url}"`;
|
|
4332
|
+
try {
|
|
4333
|
+
await execAsync(command);
|
|
4334
|
+
printer_default.info("\nOpening Microsoft download page in your browser...");
|
|
4335
|
+
printer_default.info(
|
|
4336
|
+
"Please install the Visual C++ Runtime and restart this application.\n"
|
|
4337
|
+
);
|
|
4338
|
+
} catch (err) {
|
|
4339
|
+
printer_default.info("\nUnable to open your browser automatically.");
|
|
4340
|
+
printer_default.info(
|
|
4341
|
+
"Please visit the following page to download the runtime:\n"
|
|
4342
|
+
);
|
|
4343
|
+
printer_default.info(url + "\n");
|
|
4344
|
+
}
|
|
4345
|
+
}
|
|
4346
|
+
function getVCRedistMessage() {
|
|
4347
|
+
const status = getVCRedistStatus();
|
|
4348
|
+
if (!status.required || status.installed) {
|
|
4349
|
+
return null;
|
|
4350
|
+
}
|
|
4351
|
+
return {
|
|
4352
|
+
error: true,
|
|
4353
|
+
message: "Missing Microsoft Visual C++ Runtime. This application requires the Microsoft Visual C++ Runtime to run on Windows.\nPlease download and install it using the link below, then restart this application.\n"
|
|
4354
|
+
};
|
|
4355
|
+
}
|
|
4356
|
+
|
|
4357
|
+
// src/index.ts
|
|
4358
|
+
init_printer();
|
|
4359
|
+
async function verifyAndLoad() {
|
|
4360
|
+
if (process.platform === "win32" && !hasVCRedist()) {
|
|
4361
|
+
const msg = getVCRedistMessage();
|
|
4362
|
+
printer_default.warn(
|
|
4363
|
+
msg?.message ?? "This application requires the Microsoft Visual C++ Runtime on Windows."
|
|
4364
|
+
);
|
|
4365
|
+
await openDownloadPage();
|
|
4366
|
+
process.exit(1);
|
|
4367
|
+
}
|
|
4368
|
+
await Promise.resolve().then(() => (init_main(), main_exports));
|
|
4369
|
+
}
|
|
4370
|
+
verifyAndLoad().catch((err) => {
|
|
4371
|
+
printer_default.error(`Failed to start CLI:, ${err}`);
|
|
4372
|
+
process.exit(1);
|
|
3991
4373
|
});
|