pinggy 0.6.0 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +335 -10
- package/CLAUDE.md +20 -2
- package/README.md +90 -0
- package/dist/TunnelManager-NGSUBZYI.js +13 -0
- package/dist/{TunnelTui-OZ6AVGTY.js → TunnelTui-UCG2YSLX.js} +6 -18
- package/dist/{chunk-I4HXBP5G.js → chunk-7EQKHVQX.js} +34 -2
- package/dist/{chunk-GBYF2H4H.js → chunk-AFMTIELL.js} +4 -0
- package/dist/{chunk-2HGC37SK.js → chunk-GESMZQHI.js} +47 -119
- package/dist/{chunk-76SOSXXR.js → chunk-MV56U3E6.js} +2 -2
- package/dist/{chunk-MT44NAXX.js → chunk-PD7HXVQ3.js} +1 -1
- package/dist/{chunk-J2U6P376.js → chunk-QNUVQ6KD.js} +204 -21
- package/dist/{chunk-4G6SPMMV.js → chunk-TG6LBCPG.js} +3486 -236
- package/dist/chunk-XZ64ZAGO.js +18 -0
- package/dist/{chunk-H5XJMFNT.js → chunk-Y435RMHQ.js} +7 -3
- package/dist/{configStore-LLWSTN6X.js → configStore-RR5ZU4E3.js} +3 -3
- package/dist/{daemonChild-CV2UTL6T.js → daemonChild-E73NFWKE.js} +13 -8
- package/dist/{daemonConfig-G6S46GPJ.js → daemonConfig-QVXQ47XH.js} +2 -2
- package/dist/index.cjs +3924 -281
- package/dist/index.d.cts +135 -53
- package/dist/index.d.ts +135 -53
- package/dist/index.js +13 -10
- package/dist/ipcClient-XGF2KCQO.js +10 -0
- package/dist/{main-WQ7NUQL4.js → main-M6MACH7D.js} +9 -8
- package/dist/workers/file_serve_worker.js +1 -1
- package/package.json +5 -2
- package/scripts/install/install.ps1 +261 -0
- package/scripts/install/install.sh +387 -0
- package/dist/TunnelManager-I3NW6UXU.js +0 -11
- package/dist/ipcClient-URHJKNEH.js +0 -6
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ Create secure, shareable tunnels to your localhost and manage them from the comm
|
|
|
14
14
|
- Per-tunnel and per-daemon log files with `pinggy logs` (tail, follow, rotation-safe)
|
|
15
15
|
- System-service install for auto-start at boot (systemd, launchd, Task Scheduler)
|
|
16
16
|
- Remote management via secure WebSocket connection (works with Pinggy Dashboard)
|
|
17
|
+
- Pinggy Devices: enrol the machine with `pinggy device` and keep it visible in the dashboard
|
|
17
18
|
- Save and load configuration files
|
|
18
19
|
- Config store for saving, listing, updating, and starting named tunnel configs
|
|
19
20
|
- Auto-start support for launching saved tunnels automatically
|
|
@@ -226,6 +227,92 @@ You can control tunnels remotely using a secure WebSocket connection.
|
|
|
226
227
|
```
|
|
227
228
|
|
|
228
229
|
|
|
230
|
+
## Pinggy Devices
|
|
231
|
+
|
|
232
|
+
Enrol this machine as a Pinggy device. The agent opens 1 WebSocket to the dashboard, registers the machine, and holds the connection open so the dashboard can show the device as online.
|
|
233
|
+
|
|
234
|
+
Devices and remote management are separate features. Remote management drives tunnels from the dashboard with an API key. A device is the machine itself, enrolled with its own token. The device agent owns no tunnels, and stopping it leaves every running tunnel alone.
|
|
235
|
+
|
|
236
|
+
The agent runs in the foreground CLI process, not the daemon. It needs no daemon and starts none.
|
|
237
|
+
|
|
238
|
+
### Connect a device
|
|
239
|
+
```bash
|
|
240
|
+
pinggy device connect --token <TOKEN>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Add the device in the dashboard first, then pass the token it gives you. The command stays in the foreground and reconnects on its own until you interrupt it.
|
|
244
|
+
|
|
245
|
+
The token is written to disk on the first successful connect, so later runs need no flag:
|
|
246
|
+
```bash
|
|
247
|
+
pinggy device connect
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Point the agent at a different dashboard with `--manage`:
|
|
251
|
+
```bash
|
|
252
|
+
pinggy device connect --token <TOKEN> --manage dashboard.pinggy.io
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`--manage` takes a host. The scheme becomes `wss://` unless you type `ws://` or `wss://` yourself. The agent connects to `/backend/api/v1/device-agent/ws/connect` on that host and sends the token in the `X-Pinggy-Device-Token` header.
|
|
256
|
+
|
|
257
|
+
`--token` here is the device token, not the tunnel token of the same flag name in [Options](#options). The 2 credentials are unrelated and the device agent never reads the tunnel one.
|
|
258
|
+
|
|
259
|
+
### Show what this machine remembers
|
|
260
|
+
```bash
|
|
261
|
+
pinggy device status
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Prints the device id, server, masked token, and enrolment time from the local file. It contacts nothing.
|
|
265
|
+
|
|
266
|
+
### Forget the local credential
|
|
267
|
+
```bash
|
|
268
|
+
pinggy device remove
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Deletes the local credential only. The device still exists in the dashboard. Delete it there to revoke it, because a machine that has lost its credential cannot authenticate the delete.
|
|
272
|
+
|
|
273
|
+
### Reconnect and stop conditions
|
|
274
|
+
|
|
275
|
+
The agent retries a dropped connection every 5 seconds. Some failures are terminal and it exits instead:
|
|
276
|
+
|
|
277
|
+
| Event | Agent |
|
|
278
|
+
|-------|-------|
|
|
279
|
+
| HTTP 401 on the upgrade | Stops. The token is wrong or revoked. Re-enrol from the dashboard |
|
|
280
|
+
| Dashboard refuses the handshake | Stops, and prints the reason |
|
|
281
|
+
| Device is connected from another machine | Retries. The credential is fine, the other socket has to drop first |
|
|
282
|
+
| Dashboard says it is shutting down | Retries |
|
|
283
|
+
| Any other close, or a network error | Retries |
|
|
284
|
+
|
|
285
|
+
Ctrl+C stops the retry loop. The agent exits once the current socket closes; a second Ctrl+C exits at once.
|
|
286
|
+
|
|
287
|
+
### What the agent does today
|
|
288
|
+
|
|
289
|
+
It enrols, learns its device id and heartbeat interval from the dashboard, and sends a heartbeat on that interval. CPU and memory reporting, and the browser terminal, are not built yet.
|
|
290
|
+
|
|
291
|
+
### Combining `device` with other commands
|
|
292
|
+
|
|
293
|
+
1 invocation runs 1 command. The first word decides which, and everything after it belongs to that command. `pinggy device connect start my-tunnel` runs the agent and ignores `start my-tunnel`; it does not start a tunnel.
|
|
294
|
+
|
|
295
|
+
| Command | What happens |
|
|
296
|
+
|---------|--------------|
|
|
297
|
+
| `pinggy device connect -l 3000 -b --all --remote-management <KEY>` | Runs the agent. Those flags parse and are ignored. No tunnel, no remote management, no daemon. Only `--token` and `--manage` are read |
|
|
298
|
+
| `pinggy device start`, `pinggy device daemon stop` | Error, exit 1. The verb after `device` is `login`, `connect`, `stop`, `status`, or `remove` |
|
|
299
|
+
| `pinggy start device`, `pinggy stop device` | `device` reads as a tunnel name. No new config can be called `device`, so nothing matches |
|
|
300
|
+
| `pinggy config save device ...` | Rejected. `device` is a reserved name |
|
|
301
|
+
| `pinggy -l 3000 device` | An ordinary tunnel. `device` is not a valid domain, so it warns `Unknown extended option` and is ignored |
|
|
302
|
+
| `pinggy device connect --typo` | Unknown options are rejected before anything runs |
|
|
303
|
+
| `pinggy devices connect`, or `devices` with anything | Error, exit 1: `"pinggy devices" is now "pinggy device". Run: pinggy device connect`. Nothing runs |
|
|
304
|
+
|
|
305
|
+
`pinggy device` and `pinggy device --help` print the command list. After a verb, `--help` is ignored, so `pinggy device connect --help` tries to connect.
|
|
306
|
+
|
|
307
|
+
### Devices, tunnels, and the daemon do not interfere
|
|
308
|
+
|
|
309
|
+
- The agent is its own process. `pinggy daemon stop` leaves it running, and Ctrl+C on the agent leaves every tunnel running.
|
|
310
|
+
- `pinggy device connect` never starts the daemon. Run tunnels and the agent side by side in 2 terminals.
|
|
311
|
+
- 1 enrolment per machine. The newest successful connect overwrites `device.json`, and a `--token` on the command line beats the stored one.
|
|
312
|
+
- Running the agent twice on 1 machine does not work. The dashboard refuses the second connection because the device is already connected, and that copy keeps retrying until the first one drops.
|
|
313
|
+
- `pinggy device remove` deletes the credential file but does not stop a running agent, which rewrites the file the next time it connects. Stop the agent first.
|
|
314
|
+
|
|
315
|
+
|
|
229
316
|
## Usage
|
|
230
317
|
Basic syntax:
|
|
231
318
|
pinggy [options] [user@domain]
|
|
@@ -403,6 +490,8 @@ Config dir varies by OS:
|
|
|
403
490
|
- Linux/macOS: `~/.config/pinggy/`
|
|
404
491
|
- Windows: `%APPDATA%\pinggy\`
|
|
405
492
|
|
|
493
|
+
It holds `daemon.json` (discovery), `daemon-config.json` (persisted settings), `daemon-state.json` (crash recovery), `tunnels/` (saved configs), and `device.json` (the Pinggy Devices credential, written `0600`).
|
|
494
|
+
|
|
406
495
|
Log dir varies by OS:
|
|
407
496
|
- Linux: `~/.local/state/pinggy-cli/logs/` (honors `$XDG_STATE_HOME`)
|
|
408
497
|
- macOS: `~/Library/Logs/Pinggy-CLI/`
|
|
@@ -421,6 +510,7 @@ Optionally combine with other flags (auth, IP whitelist) as needed.
|
|
|
421
510
|
- **Foreground tunnel**: Ctrl+C closes the TUI. The daemon arms a 5-second grace timer and stops the tunnel if no other CLI re-attaches.
|
|
422
511
|
- **Detached tunnel** (`-b`): the CLI already exited. Stop it with `pinggy stop <name|id>`.
|
|
423
512
|
- **Everything at once**: `pinggy daemon stop` stops every tunnel and shuts the daemon down cleanly. `daemon-state.json` is cleared, so nothing replays on next start.
|
|
513
|
+
- **Device agent** (`pinggy device connect`): Ctrl+C ends the agent. It runs outside the daemon, so no tunnel is affected.
|
|
424
514
|
|
|
425
515
|
|
|
426
516
|
## Versioning
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TunnelAlreadyRunningError,
|
|
3
|
+
TunnelManager,
|
|
4
|
+
TunnelRebuildInProgressError
|
|
5
|
+
} from "./chunk-QNUVQ6KD.js";
|
|
6
|
+
import "./chunk-UB26QJ4T.js";
|
|
7
|
+
import "./chunk-MV56U3E6.js";
|
|
8
|
+
import "./chunk-AFMTIELL.js";
|
|
9
|
+
export {
|
|
10
|
+
TunnelAlreadyRunningError,
|
|
11
|
+
TunnelManager,
|
|
12
|
+
TunnelRebuildInProgressError
|
|
13
|
+
};
|
|
@@ -1,30 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createQrCodes
|
|
3
|
+
} from "./chunk-XZ64ZAGO.js";
|
|
1
4
|
import {
|
|
2
5
|
TunnelManager
|
|
3
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-QNUVQ6KD.js";
|
|
4
7
|
import "./chunk-UB26QJ4T.js";
|
|
5
8
|
import {
|
|
6
9
|
logger
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-MV56U3E6.js";
|
|
11
|
+
import "./chunk-AFMTIELL.js";
|
|
9
12
|
|
|
10
13
|
// src/tui/blessed/TunnelTui.ts
|
|
11
14
|
import blessed3 from "blessed";
|
|
12
15
|
|
|
13
|
-
// src/tui/blessed/qrCodeGenerator.ts
|
|
14
|
-
import QRCode from "qrcode";
|
|
15
|
-
async function createQrCodes(urls) {
|
|
16
|
-
const codes = [];
|
|
17
|
-
for (const url of urls) {
|
|
18
|
-
const raw = await QRCode.toString(url, {
|
|
19
|
-
type: "utf8",
|
|
20
|
-
margin: 2,
|
|
21
|
-
errorCorrectionLevel: "L"
|
|
22
|
-
});
|
|
23
|
-
codes.push(raw);
|
|
24
|
-
}
|
|
25
|
-
return codes;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
16
|
// src/tui/blessed/webDebuggerConnection.ts
|
|
29
17
|
import WebSocket from "ws";
|
|
30
18
|
|
|
@@ -20,7 +20,10 @@ var Route = {
|
|
|
20
20
|
SetLogLevel: "POST /loglevel",
|
|
21
21
|
GetTunnelLogging: "GET /config/tunnel-logging",
|
|
22
22
|
SetTunnelLogging: "POST /config/tunnel-logging",
|
|
23
|
-
GetLogPaths: "GET /logs/paths"
|
|
23
|
+
GetLogPaths: "GET /logs/paths",
|
|
24
|
+
GetDeviceAgent: "GET /device",
|
|
25
|
+
StartDeviceAgent: "POST /device/start",
|
|
26
|
+
StopDeviceAgent: "POST /device/stop"
|
|
24
27
|
};
|
|
25
28
|
var DaemonHost = {
|
|
26
29
|
CLI: "cli",
|
|
@@ -30,6 +33,12 @@ var ShutdownStatus = {
|
|
|
30
33
|
ShuttingDown: "shutting_down",
|
|
31
34
|
RefusedInApp: "refused_in_app"
|
|
32
35
|
};
|
|
36
|
+
var DeviceAgentState = {
|
|
37
|
+
Stopped: "stopped",
|
|
38
|
+
Connecting: "connecting",
|
|
39
|
+
Connected: "connected",
|
|
40
|
+
Reconnecting: "reconnecting"
|
|
41
|
+
};
|
|
33
42
|
var SessionMode = {
|
|
34
43
|
Foreground: "foreground",
|
|
35
44
|
Detached: "detached"
|
|
@@ -37,6 +46,14 @@ var SessionMode = {
|
|
|
37
46
|
|
|
38
47
|
// src/daemon/ipc/ipcClient.ts
|
|
39
48
|
var REQUEST_TIMEOUT_MS = 1e4;
|
|
49
|
+
var IPCHttpError = class extends Error {
|
|
50
|
+
constructor(statusCode, message) {
|
|
51
|
+
super(message);
|
|
52
|
+
this.statusCode = statusCode;
|
|
53
|
+
this.name = "IPCHttpError";
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var HTTP_NOT_FOUND = 404;
|
|
40
57
|
var IPCClient = class {
|
|
41
58
|
constructor(port, origin = "cli") {
|
|
42
59
|
this.port = port;
|
|
@@ -103,6 +120,15 @@ var IPCClient = class {
|
|
|
103
120
|
async resolveLogPath(q) {
|
|
104
121
|
return this.request("GET", `/logs/resolve?q=${encodeURIComponent(q)}`);
|
|
105
122
|
}
|
|
123
|
+
async getDeviceAgentStatus(timeoutMs) {
|
|
124
|
+
return this.call(Route.GetDeviceAgent, void 0, timeoutMs);
|
|
125
|
+
}
|
|
126
|
+
async startDeviceAgent() {
|
|
127
|
+
return this.call(Route.StartDeviceAgent, {});
|
|
128
|
+
}
|
|
129
|
+
async stopDeviceAgent() {
|
|
130
|
+
return this.call(Route.StopDeviceAgent, {});
|
|
131
|
+
}
|
|
106
132
|
/**
|
|
107
133
|
* Get the WebSocket URL for event streaming.
|
|
108
134
|
*/
|
|
@@ -144,7 +170,10 @@ var IPCClient = class {
|
|
|
144
170
|
const text = Buffer.concat(chunks).toString("utf-8");
|
|
145
171
|
const statusCode = res.statusCode ?? 0;
|
|
146
172
|
if (statusCode < 200 || statusCode >= 300) {
|
|
147
|
-
reject(new
|
|
173
|
+
reject(new IPCHttpError(
|
|
174
|
+
statusCode,
|
|
175
|
+
`Daemon returned HTTP ${statusCode}: ${text.slice(0, 200)}`
|
|
176
|
+
));
|
|
148
177
|
return;
|
|
149
178
|
}
|
|
150
179
|
try {
|
|
@@ -171,6 +200,9 @@ export {
|
|
|
171
200
|
Route,
|
|
172
201
|
DaemonHost,
|
|
173
202
|
ShutdownStatus,
|
|
203
|
+
DeviceAgentState,
|
|
174
204
|
SessionMode,
|
|
205
|
+
IPCHttpError,
|
|
206
|
+
HTTP_NOT_FOUND,
|
|
175
207
|
IPCClient
|
|
176
208
|
};
|
|
@@ -57,6 +57,9 @@ function getTunnelLogPath(tunnelId, origin, name) {
|
|
|
57
57
|
function getDaemonLogPath() {
|
|
58
58
|
return path.join(getPinggyLogDir(), "daemon.log");
|
|
59
59
|
}
|
|
60
|
+
function getDeviceConfigPath() {
|
|
61
|
+
return path.join(getPinggyConfigDir(), "device.json");
|
|
62
|
+
}
|
|
60
63
|
function ensurePinggyConfigDir() {
|
|
61
64
|
const dir = getPinggyConfigDir();
|
|
62
65
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -73,5 +76,6 @@ export {
|
|
|
73
76
|
getTunnelLogDir,
|
|
74
77
|
getTunnelLogPath,
|
|
75
78
|
getDaemonLogPath,
|
|
79
|
+
getDeviceConfigPath,
|
|
76
80
|
ensurePinggyConfigDir
|
|
77
81
|
};
|
|
@@ -4,130 +4,32 @@ import {
|
|
|
4
4
|
IPC_VERSION,
|
|
5
5
|
SessionMode,
|
|
6
6
|
ShutdownStatus
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-7EQKHVQX.js";
|
|
8
8
|
import {
|
|
9
|
+
ErrorCode,
|
|
10
|
+
NewErrorResponseObject,
|
|
11
|
+
NewResponseObject,
|
|
12
|
+
RemoteManagementStatus,
|
|
9
13
|
TunnelAlreadyRunningError,
|
|
10
14
|
TunnelManager,
|
|
15
|
+
TunnelRebuildInProgressError,
|
|
11
16
|
errorMessage,
|
|
12
17
|
getLocalAddress,
|
|
13
18
|
getVersion,
|
|
19
|
+
isErrorResponse,
|
|
14
20
|
isValidPort,
|
|
21
|
+
newErrorResponse,
|
|
22
|
+
newStats,
|
|
23
|
+
newStatus,
|
|
15
24
|
printer_default
|
|
16
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-QNUVQ6KD.js";
|
|
17
26
|
import {
|
|
18
27
|
logger
|
|
19
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-MV56U3E6.js";
|
|
20
29
|
import {
|
|
21
30
|
getDaemonInfoPath,
|
|
22
31
|
getDaemonLogPath
|
|
23
|
-
} from "./chunk-
|
|
24
|
-
|
|
25
|
-
// src/types.ts
|
|
26
|
-
var TunnelStateType = /* @__PURE__ */ ((TunnelStateType2) => {
|
|
27
|
-
TunnelStateType2["New"] = "idle";
|
|
28
|
-
TunnelStateType2["Starting"] = "starting";
|
|
29
|
-
TunnelStateType2["Running"] = "running";
|
|
30
|
-
TunnelStateType2["Live"] = "live";
|
|
31
|
-
TunnelStateType2["Closed"] = "closed";
|
|
32
|
-
TunnelStateType2["Exited"] = "exited";
|
|
33
|
-
return TunnelStateType2;
|
|
34
|
-
})(TunnelStateType || {});
|
|
35
|
-
var TunnelErrorCodeType = /* @__PURE__ */ ((TunnelErrorCodeType2) => {
|
|
36
|
-
TunnelErrorCodeType2["NonResponsive"] = "non_responsive";
|
|
37
|
-
TunnelErrorCodeType2["FailedToConnect"] = "failed_to_connect";
|
|
38
|
-
TunnelErrorCodeType2["ErrorInAdditionalForwarding"] = "additional_forwarding_error";
|
|
39
|
-
TunnelErrorCodeType2["WebdebuggerError"] = "webdebugger_error";
|
|
40
|
-
TunnelErrorCodeType2["NoError"] = "";
|
|
41
|
-
return TunnelErrorCodeType2;
|
|
42
|
-
})(TunnelErrorCodeType || {});
|
|
43
|
-
var TunnelWarningCode = /* @__PURE__ */ ((TunnelWarningCode2) => {
|
|
44
|
-
TunnelWarningCode2["InvalidTunnelServePath"] = "INVALID_TUNNEL_SERVE_PATH";
|
|
45
|
-
TunnelWarningCode2["UnknownWarning"] = "UNKNOWN_WARNING";
|
|
46
|
-
return TunnelWarningCode2;
|
|
47
|
-
})(TunnelWarningCode || {});
|
|
48
|
-
var ErrorCode = {
|
|
49
|
-
InvalidRequestMethodError: "INVALID_REQUEST_METHOD",
|
|
50
|
-
InvalidRequestBodyError: "COULD_NOT_READ_BODY",
|
|
51
|
-
InternalServerError: "INTERNAL_SERVER_ERROR",
|
|
52
|
-
InvalidBodyFormatError: "INVALID_DATA_FORMAT",
|
|
53
|
-
ErrorStartingTunnel: "ERROR_STARTING_TUNNEL",
|
|
54
|
-
TunnelNotFound: "TUNNEL_WITH_ID_OR_CONFIG_ID_NOT_FOUND",
|
|
55
|
-
TunnelAlreadyRunningError: "TUNNEL_WITH_ID_OR_CONFIG_ID_ALREADY_RUNNING",
|
|
56
|
-
WebsocketUpgradeFailError: "WEBSOCKET_UPGRADE_FAILED",
|
|
57
|
-
RemoteManagementAlreadyRunning: "REMOTE_MANAGEMENT_ALREADY_RUNNING",
|
|
58
|
-
RemoteManagementNotRunning: "REMOTE_MANAGEMENT_NOT_RUNNING",
|
|
59
|
-
RemoteManagementDeserializationFailed: "REMOTE_MANAGEMENT_DESERIALIZATION_FAILED"
|
|
60
|
-
};
|
|
61
|
-
function isErrorResponse(obj) {
|
|
62
|
-
return typeof obj === "object" && obj !== null && "code" in obj && "message" in obj && typeof obj.message === "string" && Object.values(ErrorCode).includes(obj.code);
|
|
63
|
-
}
|
|
64
|
-
function newErrorResponse(codeOrError, message) {
|
|
65
|
-
if (typeof codeOrError === "object") {
|
|
66
|
-
return codeOrError;
|
|
67
|
-
}
|
|
68
|
-
return {
|
|
69
|
-
code: codeOrError,
|
|
70
|
-
message
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
function NewResponseObject(data) {
|
|
74
|
-
const encoder = new TextEncoder();
|
|
75
|
-
const bytes = encoder.encode(JSON.stringify(data));
|
|
76
|
-
return {
|
|
77
|
-
response: bytes,
|
|
78
|
-
requestid: "",
|
|
79
|
-
command: "",
|
|
80
|
-
error: false,
|
|
81
|
-
errorresponse: {}
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
function NewErrorResponseObject(errorResponse) {
|
|
85
|
-
return {
|
|
86
|
-
response: new Uint8Array(),
|
|
87
|
-
requestid: "",
|
|
88
|
-
command: "",
|
|
89
|
-
error: true,
|
|
90
|
-
errorresponse: errorResponse
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
function newStatus(tunnelState, errorCode, errorMsg) {
|
|
94
|
-
let assignedState = tunnelState;
|
|
95
|
-
if (tunnelState === "live" /* Live */) {
|
|
96
|
-
assignedState = "running" /* Running */;
|
|
97
|
-
} else if (tunnelState === "idle" /* New */) {
|
|
98
|
-
assignedState = "idle" /* New */;
|
|
99
|
-
} else if (tunnelState === "closed" /* Closed */) {
|
|
100
|
-
assignedState = "exited" /* Exited */;
|
|
101
|
-
}
|
|
102
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
103
|
-
return {
|
|
104
|
-
state: assignedState,
|
|
105
|
-
errorcode: errorCode,
|
|
106
|
-
errormsg: errorMsg,
|
|
107
|
-
createdtimestamp: now,
|
|
108
|
-
starttimestamp: now,
|
|
109
|
-
endtimestamp: now,
|
|
110
|
-
warnings: []
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
function newStats() {
|
|
114
|
-
return {
|
|
115
|
-
numLiveConnections: 0,
|
|
116
|
-
numTotalConnections: 0,
|
|
117
|
-
numTotalReqBytes: 0,
|
|
118
|
-
numTotalResBytes: 0,
|
|
119
|
-
numTotalTxBytes: 0,
|
|
120
|
-
elapsedTime: 0
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
var RemoteManagementStatus = {
|
|
124
|
-
Connecting: "CONNECTING",
|
|
125
|
-
Disconnecting: "DISCONNECTING",
|
|
126
|
-
Reconnecting: "RECONNECTING",
|
|
127
|
-
Running: "RUNNING",
|
|
128
|
-
NotRunning: "NOT_RUNNING",
|
|
129
|
-
Error: "ERROR"
|
|
130
|
-
};
|
|
32
|
+
} from "./chunk-AFMTIELL.js";
|
|
131
33
|
|
|
132
34
|
// src/remote_management/remote_schema.ts
|
|
133
35
|
import { HaProxyVersion, TunnelType } from "@pinggy/pinggy";
|
|
@@ -428,6 +330,9 @@ var TunnelOperations = class {
|
|
|
428
330
|
}
|
|
429
331
|
if (managed?.lastError) {
|
|
430
332
|
status.lastError = managed.lastError;
|
|
333
|
+
if (managed.lastError.isFatal) {
|
|
334
|
+
status.errormsg = managed.lastError.message;
|
|
335
|
+
}
|
|
431
336
|
}
|
|
432
337
|
} catch (e) {
|
|
433
338
|
}
|
|
@@ -492,6 +397,14 @@ var TunnelOperations = class {
|
|
|
492
397
|
message: err instanceof Error ? err.message : fallback
|
|
493
398
|
});
|
|
494
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* A restart or update of a tunnel another one is still rebuilding. A no-wait call checks this before
|
|
402
|
+
* it answers, because a throw from the call it does not await is only logged.
|
|
403
|
+
*/
|
|
404
|
+
rebuildInProgress(tunnelid) {
|
|
405
|
+
const err = new TunnelRebuildInProgressError(tunnelid);
|
|
406
|
+
return this.error(ErrorCode.TunnelAlreadyRunningError, err, err.message);
|
|
407
|
+
}
|
|
495
408
|
// --- Operations ---
|
|
496
409
|
async handleStart(config, noWait = false, origin = "cli") {
|
|
497
410
|
try {
|
|
@@ -554,6 +467,7 @@ var TunnelOperations = class {
|
|
|
554
467
|
if (noWait) {
|
|
555
468
|
const existing = this.tunnelManager.getManagedTunnel(config.configid);
|
|
556
469
|
if (!existing.tunnelConfig) throw new Error("Invalid tunnel state before configuration update");
|
|
470
|
+
if (existing.rebuild) return this.rebuildInProgress(existing.tunnelid);
|
|
557
471
|
if (!existing.isStopped) {
|
|
558
472
|
this.tunnelManager.updateConfig(updateOpts).catch((err) => {
|
|
559
473
|
logger.error("No-wait updateConfig failed", { configid: config.configid, err: String(err) });
|
|
@@ -574,6 +488,7 @@ var TunnelOperations = class {
|
|
|
574
488
|
if (noWait) {
|
|
575
489
|
const existing = this.tunnelManager.getManagedTunnel(config.configId);
|
|
576
490
|
if (!existing.tunnelConfig) throw new Error("Invalid tunnel state before configuration update");
|
|
491
|
+
if (existing.rebuild) return this.rebuildInProgress(existing.tunnelid);
|
|
577
492
|
if (!existing.isStopped) {
|
|
578
493
|
this.tunnelManager.updateConfig(config).catch((err) => {
|
|
579
494
|
logger.error("No-wait updateConfigV2 failed", { configId: config.configId, err: String(err) });
|
|
@@ -672,6 +587,7 @@ var TunnelOperations = class {
|
|
|
672
587
|
if (noWait) {
|
|
673
588
|
const managed2 = this.tunnelManager.getManagedTunnel("", tunnelid);
|
|
674
589
|
if (!managed2?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
|
|
590
|
+
if (managed2.rebuild) return this.rebuildInProgress(tunnelid);
|
|
675
591
|
this.tunnelManager.restartTunnel(tunnelid).catch((err) => {
|
|
676
592
|
logger.error("No-wait restartTunnel failed", { tunnelid, err: String(err) });
|
|
677
593
|
});
|
|
@@ -682,6 +598,9 @@ var TunnelOperations = class {
|
|
|
682
598
|
if (!managed?.tunnelConfig) throw new Error(`Tunnel config for ID "${tunnelid}" not found`);
|
|
683
599
|
return this.buildTunnelResponse(tunnelid, managed.tunnelConfig, managed.configId, managed.tunnelName, managed.serve);
|
|
684
600
|
} catch (err) {
|
|
601
|
+
if (err instanceof TunnelRebuildInProgressError) {
|
|
602
|
+
return this.rebuildInProgress(tunnelid);
|
|
603
|
+
}
|
|
685
604
|
return this.error(ErrorCode.TunnelNotFound, err, "Failed to restart tunnel");
|
|
686
605
|
}
|
|
687
606
|
}
|
|
@@ -1682,7 +1601,7 @@ async function ensureDaemonRunning() {
|
|
|
1682
1601
|
return existing;
|
|
1683
1602
|
}
|
|
1684
1603
|
if (process.versions.electron) {
|
|
1685
|
-
const { runDaemonChild } = await import("./daemonChild-
|
|
1604
|
+
const { runDaemonChild } = await import("./daemonChild-E73NFWKE.js");
|
|
1686
1605
|
const handle = await runDaemonChild({
|
|
1687
1606
|
installSignalHandlers: false,
|
|
1688
1607
|
exitOnFailure: false,
|
|
@@ -1705,7 +1624,7 @@ function getInProcessDaemonHandle() {
|
|
|
1705
1624
|
async function getActiveTunnelSummaries(origin = "app") {
|
|
1706
1625
|
const info = getDaemonInfo();
|
|
1707
1626
|
if (!info) return [];
|
|
1708
|
-
const { IPCClient: IPCClient2 } = await import("./ipcClient-
|
|
1627
|
+
const { IPCClient: IPCClient2 } = await import("./ipcClient-XGF2KCQO.js");
|
|
1709
1628
|
const client = new IPCClient2(info.port, origin);
|
|
1710
1629
|
let tunnels;
|
|
1711
1630
|
try {
|
|
@@ -1752,7 +1671,7 @@ async function stopDaemon() {
|
|
|
1752
1671
|
if (!info) return { ok: false, error: "No daemon is running." };
|
|
1753
1672
|
let daemonErrors = [];
|
|
1754
1673
|
try {
|
|
1755
|
-
const { IPCClient: IPCClient2 } = await import("./ipcClient-
|
|
1674
|
+
const { IPCClient: IPCClient2 } = await import("./ipcClient-XGF2KCQO.js");
|
|
1756
1675
|
const client = new IPCClient2(info.port);
|
|
1757
1676
|
const result = await client.shutdown();
|
|
1758
1677
|
logger.debug("Sent shutdown command to daemon", { result });
|
|
@@ -2297,6 +2216,20 @@ var TunnelClient = class _TunnelClient {
|
|
|
2297
2216
|
this.assertClient();
|
|
2298
2217
|
await this.ipc.restartTunnel(tunnelId);
|
|
2299
2218
|
}
|
|
2219
|
+
// Device agent (`pinggy device connect -b`). The daemon reads the token and the server from
|
|
2220
|
+
// device.json, so none of these takes either.
|
|
2221
|
+
async getDeviceAgentStatus() {
|
|
2222
|
+
this.assertClient();
|
|
2223
|
+
return this.ipc.getDeviceAgentStatus();
|
|
2224
|
+
}
|
|
2225
|
+
async startDeviceAgent() {
|
|
2226
|
+
this.assertClient();
|
|
2227
|
+
return this.ipc.startDeviceAgent();
|
|
2228
|
+
}
|
|
2229
|
+
async stopDeviceAgent() {
|
|
2230
|
+
this.assertClient();
|
|
2231
|
+
return this.ipc.stopDeviceAgent();
|
|
2232
|
+
}
|
|
2300
2233
|
// Streaming — delegate to WsStream, guarded by daemon-lost
|
|
2301
2234
|
async attach(tunnelId, mode = SessionMode.Foreground) {
|
|
2302
2235
|
if (this.health.isLost()) return;
|
|
@@ -2432,11 +2365,6 @@ var TunnelClient = class _TunnelClient {
|
|
|
2432
2365
|
};
|
|
2433
2366
|
|
|
2434
2367
|
export {
|
|
2435
|
-
TunnelStateType,
|
|
2436
|
-
TunnelErrorCodeType,
|
|
2437
|
-
TunnelWarningCode,
|
|
2438
|
-
ErrorCode,
|
|
2439
|
-
isErrorResponse,
|
|
2440
2368
|
TunnelConfigV1Schema,
|
|
2441
2369
|
TUNNEL_CONFIG_V1_KEYS,
|
|
2442
2370
|
TunnelOperations,
|
|
@@ -14,9 +14,9 @@ function setLogLevel(level) {
|
|
|
14
14
|
if (_sdkLogFilePath) {
|
|
15
15
|
enableLoggingByLogLevelInSdk(level, _sdkLogFilePath);
|
|
16
16
|
}
|
|
17
|
-
import("./daemonConfig-
|
|
17
|
+
import("./daemonConfig-QVXQ47XH.js").then(({ writeDaemonConfig }) => writeDaemonConfig({ logLevel: level })).catch(() => {
|
|
18
18
|
});
|
|
19
|
-
import("./TunnelManager-
|
|
19
|
+
import("./TunnelManager-NGSUBZYI.js").then(({ TunnelManager }) => TunnelManager.getInstance().applyLogLevelToActiveTunnels(level)).catch(() => {
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
var _logger = null;
|