pinggy 0.1.0
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/LICENSE +201 -0
- package/README.md +182 -0
- package/dist/TunnelConfig.js +39 -0
- package/dist/TunnelManager.js +155 -0
- package/dist/cli/buildConfig.js +323 -0
- package/dist/cli/defaults.js +18 -0
- package/dist/cli/extendedOptions.js +134 -0
- package/dist/cli/help.js +37 -0
- package/dist/cli/options.js +36 -0
- package/dist/cli/remoteManagement.js +167 -0
- package/dist/cli/starCli.js +62 -0
- package/dist/cli/websocket_handlers.js +160 -0
- package/dist/index.js +60 -0
- package/dist/logger.js +67 -0
- package/dist/remote_management/handler.js +182 -0
- package/dist/remote_management/remoteManagement.js +159 -0
- package/dist/remote_management/remote_schema.js +122 -0
- package/dist/remote_management/websocket_handlers.js +163 -0
- package/dist/tui/asciArt.js +7 -0
- package/dist/tui/index.js +75 -0
- package/dist/tui/useTerminalSize.js +21 -0
- package/dist/tunnel_manager/TunnelManager.js +551 -0
- package/dist/tunnel_manager/handler.js +102 -0
- package/dist/tunnel_manager/remote_schema.js +106 -0
- package/dist/types.js +105 -0
- package/dist/utils/parseArgs.js +7 -0
- package/dist/utils/printer.js +68 -0
- package/dist/utils/util.js +3 -0
- package/jest.config.js +19 -0
- package/package.json +49 -0
- package/src/TunnelConfig.ts +40 -0
- package/src/_tests_/build_config.test.ts +91 -0
- package/src/cli/buildConfig.ts +357 -0
- package/src/cli/defaults.ts +20 -0
- package/src/cli/extendedOptions.ts +134 -0
- package/src/cli/help.ts +41 -0
- package/src/cli/options.ts +46 -0
- package/src/cli/starCli.tsx +118 -0
- package/src/cli/worker.ts +72 -0
- package/src/index.ts +65 -0
- package/src/logger.ts +86 -0
- package/src/remote_management/handler.ts +199 -0
- package/src/remote_management/remoteManagement.ts +168 -0
- package/src/remote_management/remote_schema.ts +134 -0
- package/src/remote_management/websocket_handlers.ts +166 -0
- package/src/tui/asciArt.ts +7 -0
- package/src/tui/hooks/useQrCodes.ts +27 -0
- package/src/tui/hooks/useReqResHeaders.ts +27 -0
- package/src/tui/hooks/useTerminalSize.ts +26 -0
- package/src/tui/hooks/useTerminalStats.ts +24 -0
- package/src/tui/hooks/useWebDebugger.ts +98 -0
- package/src/tui/index.tsx +221 -0
- package/src/tui/layout/Borders.tsx +15 -0
- package/src/tui/layout/Container.tsx +15 -0
- package/src/tui/sections/DebuggerDetailModal.tsx +53 -0
- package/src/tui/sections/KeyBindings.tsx +58 -0
- package/src/tui/sections/QrCodeSection.tsx +28 -0
- package/src/tui/sections/StatsSection.tsx +20 -0
- package/src/tui/sections/URLsSection.tsx +53 -0
- package/src/tui/utils/utils.ts +35 -0
- package/src/tunnel_manager/TunnelManager.ts +646 -0
- package/src/types.ts +234 -0
- package/src/utils/getFreePort.ts +41 -0
- package/src/utils/parseArgs.ts +29 -0
- package/src/utils/printer.ts +79 -0
- package/src/utils/util.ts +13 -0
- package/tsconfig.jest.json +8 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.buildRemoteManagementWsUrl = buildRemoteManagementWsUrl;
|
|
16
|
+
exports.parseRemoteManagement = parseRemoteManagement;
|
|
17
|
+
exports.initiateRemoteManagement = initiateRemoteManagement;
|
|
18
|
+
const ws_1 = __importDefault(require("ws"));
|
|
19
|
+
const logger_1 = require("../logger");
|
|
20
|
+
const websocket_handlers_1 = require("./websocket_handlers");
|
|
21
|
+
const RECONNECT_SLEEP_MS = 5000; // 5 seconds
|
|
22
|
+
const PING_INTERVAL_MS = 30000; // 30 seconds
|
|
23
|
+
function buildRemoteManagementWsUrl(manage) {
|
|
24
|
+
let baseUrl = (manage || "dashboard.pinggy.io").trim();
|
|
25
|
+
if (!(baseUrl.startsWith("ws://") || baseUrl.startsWith("wss://"))) {
|
|
26
|
+
baseUrl = "wss://" + baseUrl;
|
|
27
|
+
}
|
|
28
|
+
// Avoid duplicate slashes when concatenating
|
|
29
|
+
const trimmed = baseUrl.replace(/\/$/, "");
|
|
30
|
+
return `${trimmed}/backend/api/v1/remote-management/connect`;
|
|
31
|
+
}
|
|
32
|
+
function extractHostname(u) {
|
|
33
|
+
try {
|
|
34
|
+
const url = new URL(u);
|
|
35
|
+
return url.host;
|
|
36
|
+
}
|
|
37
|
+
catch (_a) {
|
|
38
|
+
return u;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function sleep(ms) {
|
|
42
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
43
|
+
}
|
|
44
|
+
function parseRemoteManagement(values) {
|
|
45
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
46
|
+
const rmToken = values["remote-management"];
|
|
47
|
+
if (typeof rmToken === 'string' && rmToken.trim().length > 0) {
|
|
48
|
+
const manageHost = values["manage"];
|
|
49
|
+
try {
|
|
50
|
+
yield initiateRemoteManagement(rmToken, manageHost);
|
|
51
|
+
return; // Exit after initiating remote management
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
logger_1.logger.error("Failed to initiate remote management:", e);
|
|
55
|
+
return { Error: e };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Initiate remote management mode with a WebSocket connection.
|
|
62
|
+
* - Connect with Authorization: Bearer <token>
|
|
63
|
+
* - On HTTP 401: print Unauthorized and exit
|
|
64
|
+
* - On other failures: retry every 15 seconds
|
|
65
|
+
* - Keep running until closed or SIGINT
|
|
66
|
+
*/
|
|
67
|
+
function initiateRemoteManagement(token, manage) {
|
|
68
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
69
|
+
if (!token || token.trim().length === 0) {
|
|
70
|
+
throw new Error("Remote management token is required (use --remote-management <TOKEN>)");
|
|
71
|
+
}
|
|
72
|
+
const wsUrl = buildRemoteManagementWsUrl(manage);
|
|
73
|
+
const wsHost = extractHostname(wsUrl);
|
|
74
|
+
logger_1.logger.info("Remote management mode enabled.");
|
|
75
|
+
// Ensure process exits cleanly on Ctrl+C
|
|
76
|
+
let stopRequested = false;
|
|
77
|
+
const sigintHandler = () => { stopRequested = true; };
|
|
78
|
+
process.once('SIGINT', sigintHandler);
|
|
79
|
+
let firstTry = true;
|
|
80
|
+
while (!stopRequested) {
|
|
81
|
+
if (firstTry) {
|
|
82
|
+
firstTry = false;
|
|
83
|
+
console.log(`Connecting to ${wsHost}`);
|
|
84
|
+
logger_1.logger.info("Connecting to remote management", { wsUrl });
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
console.log(`Reconnecting in ${RECONNECT_SLEEP_MS / 1000} seconds.`);
|
|
88
|
+
logger_1.logger.info("Reconnecting after sleep", { seconds: RECONNECT_SLEEP_MS / 1000 });
|
|
89
|
+
yield sleep(RECONNECT_SLEEP_MS);
|
|
90
|
+
if (stopRequested)
|
|
91
|
+
break;
|
|
92
|
+
console.log(`Connecting to ${wsHost}`);
|
|
93
|
+
logger_1.logger.info("Connecting to remote management", { wsUrl });
|
|
94
|
+
}
|
|
95
|
+
const ws = new ws_1.default(wsUrl, {
|
|
96
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
97
|
+
});
|
|
98
|
+
yield new Promise((resolve) => {
|
|
99
|
+
let heartbeat;
|
|
100
|
+
const startHeartbeat = () => {
|
|
101
|
+
heartbeat = setInterval(() => {
|
|
102
|
+
if (ws.readyState !== ws_1.default.OPEN)
|
|
103
|
+
return;
|
|
104
|
+
ws.ping(); // ask server for pong
|
|
105
|
+
}, PING_INTERVAL_MS);
|
|
106
|
+
};
|
|
107
|
+
ws.on("open", () => {
|
|
108
|
+
console.log(`Connected to ${wsHost}`);
|
|
109
|
+
startHeartbeat();
|
|
110
|
+
});
|
|
111
|
+
// Respond to server pings
|
|
112
|
+
ws.on("ping", () => ws.pong());
|
|
113
|
+
let firstMessage = true;
|
|
114
|
+
ws.on('message', (data) => __awaiter(this, void 0, void 0, function* () {
|
|
115
|
+
try {
|
|
116
|
+
if (firstMessage) {
|
|
117
|
+
firstMessage = false;
|
|
118
|
+
console.log("First msg", data.toString("utf-8"));
|
|
119
|
+
const ok = (0, websocket_handlers_1.handleConnectionStatusMessage)(data);
|
|
120
|
+
if (!ok) {
|
|
121
|
+
// The status message itself indicates failure, so close and retry.
|
|
122
|
+
ws.close();
|
|
123
|
+
}
|
|
124
|
+
return; // Wait for the next message (commands)
|
|
125
|
+
}
|
|
126
|
+
const text = data.toString('utf8');
|
|
127
|
+
console.log("WebSocket message received", { text });
|
|
128
|
+
const req = JSON.parse(text);
|
|
129
|
+
console.log("req in management", req);
|
|
130
|
+
const webSocketHandler = new websocket_handlers_1.WebSocketCommandHandler();
|
|
131
|
+
yield webSocketHandler.handle(ws, req);
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
logger_1.logger.warn("Failed handling websocket message", { error: String(e) });
|
|
135
|
+
}
|
|
136
|
+
}));
|
|
137
|
+
ws.on('unexpected-response', (_req, res) => {
|
|
138
|
+
if (res.statusCode === 401) {
|
|
139
|
+
console.error("Unauthorized. Please enter a valid token.");
|
|
140
|
+
logger_1.logger.error("Unauthorized (401) on remote management connect");
|
|
141
|
+
stopRequested = true; // Mark to stop retrying
|
|
142
|
+
ws.close(); // This will trigger 'close' and resolve the promise
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
logger_1.logger.warn("Unexpected HTTP response on WebSocket connect", { statusCode: res.statusCode });
|
|
146
|
+
ws.close(); // Trigger 'close' to retry
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
ws.on('close', (code, reason) => {
|
|
150
|
+
logger_1.logger.info("WebSocket closed", { code, reason: reason.toString() });
|
|
151
|
+
console.log("Connection closed.");
|
|
152
|
+
clearInterval(heartbeat);
|
|
153
|
+
resolve(); // End the promise, allowing the while loop to continue
|
|
154
|
+
});
|
|
155
|
+
ws.on('error', (err) => {
|
|
156
|
+
logger_1.logger.warn("WebSocket error", { error: err.message });
|
|
157
|
+
clearInterval(heartbeat);
|
|
158
|
+
resolve(); // End the promise, allowing the while loop to continue
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
if (stopRequested)
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
process.removeListener('SIGINT', sigintHandler);
|
|
165
|
+
logger_1.logger.info("Remote management stopped.");
|
|
166
|
+
});
|
|
167
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
|
+
import CLIPrinter from "../utils/printer.js";
|
|
12
|
+
import chalk from "chalk";
|
|
13
|
+
import TunnelTui from "../tui/index.js";
|
|
14
|
+
import { withFullScreen } from "fullscreen-ink";
|
|
15
|
+
export function startCli(finalConfig, manager) {
|
|
16
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
17
|
+
try {
|
|
18
|
+
let tunnelListenerId;
|
|
19
|
+
CLIPrinter.startSpinner("Connecting to Pinggy...");
|
|
20
|
+
const tunnel = manager.createTunnel(finalConfig);
|
|
21
|
+
if (!finalConfig.NoTUI) {
|
|
22
|
+
tunnelListenerId = manager.registerStatsListener(tunnel.tunnelid, (tunnelId, stats) => {
|
|
23
|
+
var _a;
|
|
24
|
+
// Emit stats to TUI via global callback
|
|
25
|
+
(_a = globalThis.__PINGGY_TUNNEL_STATS__) === null || _a === void 0 ? void 0 : _a.call(globalThis, stats);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
yield manager.startTunnel(tunnel.tunnelid);
|
|
29
|
+
CLIPrinter.stopSpinnerSuccess("Connected to Pinggy");
|
|
30
|
+
const urls = manager.getTunnelUrls(tunnel.tunnelid);
|
|
31
|
+
const greet = manager.getTunnelGreetMessage(tunnel.tunnelid);
|
|
32
|
+
CLIPrinter.success(chalk.bold("Tunnel established!"));
|
|
33
|
+
CLIPrinter.print(chalk.gray("───────────────────────────────"));
|
|
34
|
+
CLIPrinter.info(chalk.cyanBright("Remote URLs:"));
|
|
35
|
+
urls.forEach((url) => CLIPrinter.print(" " + chalk.magentaBright(url)));
|
|
36
|
+
CLIPrinter.print(chalk.gray("───────────────────────────────"));
|
|
37
|
+
// handle greet messages
|
|
38
|
+
if (greet === null || greet === void 0 ? void 0 : greet.includes("not authenticated")) {
|
|
39
|
+
// show unauthenticated warning
|
|
40
|
+
CLIPrinter.warn(chalk.yellowBright(greet));
|
|
41
|
+
}
|
|
42
|
+
else if (greet === null || greet === void 0 ? void 0 : greet.includes("authenticated as")) {
|
|
43
|
+
// extract email
|
|
44
|
+
const emailMatch = /authenticated as (.+)/.exec(greet);
|
|
45
|
+
const email = emailMatch ? emailMatch[1] : greet;
|
|
46
|
+
CLIPrinter.info("Authenticated as: " + chalk.greenBright(email));
|
|
47
|
+
}
|
|
48
|
+
if (!finalConfig.NoTUI) {
|
|
49
|
+
const tui = withFullScreen(_jsx(TunnelTui, { urls: urls, greet: greet || "", tunnelConfig: finalConfig }));
|
|
50
|
+
yield tui.start();
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
CLIPrinter.print(chalk.gray("\nPress Ctrl+C to stop the tunnel.\n"));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
CLIPrinter.stopSpinnerFail("Failed to connect");
|
|
58
|
+
CLIPrinter.error(err.message || "Unknown error");
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.WebSocketCommandHandler = void 0;
|
|
16
|
+
exports.handleConnectionStatusMessage = handleConnectionStatusMessage;
|
|
17
|
+
const logger_1 = require("../logger");
|
|
18
|
+
const types_1 = require("../types");
|
|
19
|
+
const handler_1 = require("../tunnel_manager/handler");
|
|
20
|
+
const remote_schema_1 = require("../tunnel_manager/remote_schema");
|
|
21
|
+
const zod_1 = __importDefault(require("zod"));
|
|
22
|
+
class WebSocketCommandHandler {
|
|
23
|
+
constructor() {
|
|
24
|
+
this.tunnelHandler = new handler_1.TunnelOperations();
|
|
25
|
+
}
|
|
26
|
+
safeParse(text) {
|
|
27
|
+
if (!text)
|
|
28
|
+
return undefined;
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(text);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
logger_1.logger.warn("Invalid JSON payload", { error: String(e), text });
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
sendResponse(ws, resp) {
|
|
38
|
+
const payload = Object.assign(Object.assign({}, resp), { response: Buffer.from(resp.response || []).toString("base64") });
|
|
39
|
+
ws.send(JSON.stringify(payload));
|
|
40
|
+
}
|
|
41
|
+
sendError(ws, req, message, code = types_1.ErrorCode.InternalServerError) {
|
|
42
|
+
const resp = (0, types_1.NewErrorResponseObject)({ code, message });
|
|
43
|
+
resp.command = req.command || "";
|
|
44
|
+
resp.requestid = req.requestid || "";
|
|
45
|
+
this.sendResponse(ws, resp);
|
|
46
|
+
}
|
|
47
|
+
handleStartReq(req, raw) {
|
|
48
|
+
console.log("Handle Start Request :", req.requestid);
|
|
49
|
+
const dc = remote_schema_1.StartSchema.parse(raw);
|
|
50
|
+
const result = this.tunnelHandler.handleStart(dc.tunnelConfig);
|
|
51
|
+
console.log("Start result", result);
|
|
52
|
+
return this.wrapResponse(result, req);
|
|
53
|
+
}
|
|
54
|
+
handleStopReq(req, raw) {
|
|
55
|
+
const dc = remote_schema_1.StopSchema.parse(raw);
|
|
56
|
+
const result = this.tunnelHandler.handleStop(dc.tunnelID);
|
|
57
|
+
return this.wrapResponse(result, req);
|
|
58
|
+
}
|
|
59
|
+
handleGetReq(req, raw) {
|
|
60
|
+
const dc = remote_schema_1.GetSchema.parse(raw);
|
|
61
|
+
const result = this.tunnelHandler.handleGet(dc.tunnelID);
|
|
62
|
+
return this.wrapResponse(result, req);
|
|
63
|
+
}
|
|
64
|
+
handleRestartReq(req, raw) {
|
|
65
|
+
const dc = remote_schema_1.RestartSchema.parse(raw);
|
|
66
|
+
const result = this.tunnelHandler.handleRestart(dc.tunnelID);
|
|
67
|
+
return this.wrapResponse(result, req);
|
|
68
|
+
}
|
|
69
|
+
handleUpdateConfigReq(req, raw) {
|
|
70
|
+
const dc = remote_schema_1.UpdateConfigSchema.parse(raw);
|
|
71
|
+
const result = this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig);
|
|
72
|
+
return this.wrapResponse(result, req);
|
|
73
|
+
}
|
|
74
|
+
handleListReq(req) {
|
|
75
|
+
const result = this.tunnelHandler.handleList();
|
|
76
|
+
return this.wrapResponse(result, req);
|
|
77
|
+
}
|
|
78
|
+
wrapResponse(result, req) {
|
|
79
|
+
if ((0, types_1.isErrorResponse)(result)) {
|
|
80
|
+
console.log("Error Response:", result);
|
|
81
|
+
const errResp = (0, types_1.NewErrorResponseObject)(result);
|
|
82
|
+
errResp.command = req.command;
|
|
83
|
+
errResp.requestid = req.requestid;
|
|
84
|
+
return errResp;
|
|
85
|
+
}
|
|
86
|
+
const respObj = (0, types_1.NewResponseObject)(result);
|
|
87
|
+
respObj.command = req.command;
|
|
88
|
+
respObj.requestid = req.requestid;
|
|
89
|
+
console.log("Response Object:", respObj);
|
|
90
|
+
return respObj;
|
|
91
|
+
}
|
|
92
|
+
handle(ws, req) {
|
|
93
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
94
|
+
console.log("request received", req);
|
|
95
|
+
const cmd = (req.command || "").toLowerCase();
|
|
96
|
+
const raw = this.safeParse(req.data);
|
|
97
|
+
try {
|
|
98
|
+
let response;
|
|
99
|
+
switch (cmd) {
|
|
100
|
+
case "start": {
|
|
101
|
+
response = this.handleStartReq(req, raw);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case "stop": {
|
|
105
|
+
response = this.handleStopReq(req, raw);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case "get": {
|
|
109
|
+
response = this.handleGetReq(req, raw);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case "restart": {
|
|
113
|
+
response = this.handleRestartReq(req, raw);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case "updateconfig": {
|
|
117
|
+
response = this.handleUpdateConfigReq(req, raw);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
case "list": {
|
|
121
|
+
response = this.handleListReq(req);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
default:
|
|
125
|
+
return this.sendError(ws, req, "Invalid command");
|
|
126
|
+
}
|
|
127
|
+
console.log("Sending response", response);
|
|
128
|
+
this.sendResponse(ws, response);
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
if (e instanceof zod_1.default.ZodError) {
|
|
132
|
+
logger_1.logger.warn("Validation failed", { cmd, issues: e.issues });
|
|
133
|
+
return this.sendError(ws, req, "Invalid request data", types_1.ErrorCode.InvalidBodyFormatError);
|
|
134
|
+
}
|
|
135
|
+
logger_1.logger.error("Error handling command", { cmd, error: String(e) });
|
|
136
|
+
return this.sendError(ws, req, (e === null || e === void 0 ? void 0 : e.message) || "Internal error");
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
exports.WebSocketCommandHandler = WebSocketCommandHandler;
|
|
142
|
+
// // Returns true if connection status is OK else sends logs and returns false
|
|
143
|
+
function handleConnectionStatusMessage(firstMessage) {
|
|
144
|
+
try {
|
|
145
|
+
const text = typeof firstMessage === 'string' ? firstMessage : firstMessage.toString();
|
|
146
|
+
const cs = JSON.parse(text);
|
|
147
|
+
if (!cs.success) {
|
|
148
|
+
const msg = cs.error_msg || "Connection failed";
|
|
149
|
+
console.log("Connection failed:", msg);
|
|
150
|
+
logger_1.logger.warn("Remote management connection failed", { error_code: cs.error_code, error_msg: msg });
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
logger_1.logger.warn("Failed to parse connection status message", { error: String(e) });
|
|
157
|
+
// If parsing fails, assume connection is okay
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
import { TunnelManager } from "./tunnel_manager/TunnelManager.js";
|
|
12
|
+
import { printHelpMessage } from "./cli/help.js";
|
|
13
|
+
import { cliOptions } from "./cli/options.js";
|
|
14
|
+
import { buildFinalConfig } from "./cli/buildConfig.js";
|
|
15
|
+
import { configureLogger, logger } from "./logger.js";
|
|
16
|
+
import { parseRemoteManagement } from "./remote_management/remoteManagement.js";
|
|
17
|
+
import { parseCliArgs } from "./utils/parseArgs.js";
|
|
18
|
+
import CLIPrinter from "./utils/printer.js";
|
|
19
|
+
import { startCli } from "./cli/starCli.js";
|
|
20
|
+
function main() {
|
|
21
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
22
|
+
try {
|
|
23
|
+
// Parse arguments from the command line
|
|
24
|
+
const { values, positionals } = parseCliArgs(cliOptions);
|
|
25
|
+
// Configure logger from CLI args
|
|
26
|
+
configureLogger(values);
|
|
27
|
+
if (values.help) {
|
|
28
|
+
printHelpMessage();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
// Remote management mode
|
|
32
|
+
const parseResult = yield parseRemoteManagement(values);
|
|
33
|
+
if ((parseResult === null || parseResult === void 0 ? void 0 : parseResult.ok) === false) {
|
|
34
|
+
CLIPrinter.error(parseResult.error);
|
|
35
|
+
logger.error("Failed to initiate remote management:", parseResult.error);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
// Build final configuration from parsed args
|
|
39
|
+
logger.debug("Building final config from CLI values and positionals", { values, positionals });
|
|
40
|
+
const finalConfig = buildFinalConfig(values, positionals);
|
|
41
|
+
logger.debug("Final configuration built", finalConfig);
|
|
42
|
+
// Use the TunnelManager to start the tunnel
|
|
43
|
+
const manager = TunnelManager.getInstance();
|
|
44
|
+
const tunnel = yield startCli(finalConfig, manager);
|
|
45
|
+
// Keep the process alive and handle graceful shutdown
|
|
46
|
+
process.on('SIGINT', () => {
|
|
47
|
+
logger.info("SIGINT received: stopping tunnels and exiting");
|
|
48
|
+
console.log("\nStopping all tunnels...");
|
|
49
|
+
manager.stopAllTunnels();
|
|
50
|
+
console.log("Tunnels stopped. Exiting.");
|
|
51
|
+
process.exit(0);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
logger.error("Unhandled error in CLI:", error);
|
|
56
|
+
CLIPrinter.error(error);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
main();
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import winston from "winston";
|
|
13
|
+
import fs from "fs";
|
|
14
|
+
import path from "path";
|
|
15
|
+
// Singleton logger instance
|
|
16
|
+
let _logger = null;
|
|
17
|
+
function getLogger() {
|
|
18
|
+
if (!_logger) {
|
|
19
|
+
_logger = winston.createLogger({ level: "info", silent: true });
|
|
20
|
+
}
|
|
21
|
+
return _logger;
|
|
22
|
+
}
|
|
23
|
+
export const logger = getLogger();
|
|
24
|
+
export function configureLogger(values, silent = false) {
|
|
25
|
+
// Parse values from CLI args
|
|
26
|
+
const levelStr = values.loglevel || undefined;
|
|
27
|
+
const filePath = values.logfile || process.env.PINGGY_LOG_FILE || undefined;
|
|
28
|
+
const printlog = values.printlog;
|
|
29
|
+
// Ensure log directory exists if file logging is enabled
|
|
30
|
+
if (filePath) {
|
|
31
|
+
const dir = path.dirname(filePath);
|
|
32
|
+
if (!fs.existsSync(dir))
|
|
33
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
const transports = [];
|
|
36
|
+
// Console transport: only if explicitly requested or env var is set
|
|
37
|
+
const stdoutEnabled = printlog === true || (process.env.PINGGY_LOG_STDOUT || "").toLowerCase() === "true";
|
|
38
|
+
if (stdoutEnabled) {
|
|
39
|
+
transports.push(new winston.transports.Console({
|
|
40
|
+
format: winston.format.combine(winston.format.colorize(), winston.format.timestamp(), winston.format.printf((_a) => {
|
|
41
|
+
var { level, message, timestamp } = _a, meta = __rest(_a, ["level", "message", "timestamp"]);
|
|
42
|
+
return `${timestamp} [${level}] ${message} ${Object.keys(meta).length ? JSON.stringify(meta) : ""}`;
|
|
43
|
+
})),
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
// File transport
|
|
47
|
+
if (filePath) {
|
|
48
|
+
transports.push(new winston.transports.File({
|
|
49
|
+
filename: filePath,
|
|
50
|
+
format: winston.format.combine(winston.format.colorize(), winston.format.timestamp(), winston.format.printf((_a) => {
|
|
51
|
+
var { level, message, timestamp } = _a, meta = __rest(_a, ["level", "message", "timestamp"]);
|
|
52
|
+
return `${timestamp} [${level}] ${message} ${Object.keys(meta).length ? JSON.stringify(meta) : ""}`;
|
|
53
|
+
})),
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
const level = (levelStr || process.env.PINGGY_LOG_LEVEL || "info").toLowerCase();
|
|
57
|
+
// Mutate the singleton logger instead of replacing it so all imports keep the same instance.
|
|
58
|
+
const log = getLogger();
|
|
59
|
+
// Remove existing transports and add the new ones
|
|
60
|
+
log.clear();
|
|
61
|
+
for (const t of transports) {
|
|
62
|
+
log.add(t);
|
|
63
|
+
}
|
|
64
|
+
log.level = level;
|
|
65
|
+
log.silent = transports.length === 0 || silent === true;
|
|
66
|
+
return log;
|
|
67
|
+
}
|