pinggy 0.5.6 → 0.5.8
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/CLAUDE.md +15 -2
- package/README.md +1 -1
- package/dist/{chunk-HN4YLM7U.js → chunk-B6IKYBLU.js} +112 -15
- package/dist/{chunk-YTJEV27N.js → chunk-I4HXBP5G.js} +2 -0
- package/dist/{chunk-OH6FSLYF.js → chunk-P7XRY5N4.js} +31 -12
- package/dist/{daemonChild-3ONQO2XI.js → daemonChild-ZPZK5F4Q.js} +3 -3
- package/dist/index.cjs +132 -21
- package/dist/index.d.cts +20 -8
- package/dist/index.d.ts +20 -8
- package/dist/index.js +3 -3
- package/dist/{ipcClient-XRL2WUUM.js → ipcClient-URHJKNEH.js} +1 -1
- package/dist/{main-WXFARRMN.js → main-VBWX7HTB.js} +3 -3
- package/package.json +2 -2
package/CLAUDE.md
CHANGED
|
@@ -48,7 +48,7 @@ CLI code calls `TunnelClient` (`src/daemon/tunnelClient.ts`), the public facade
|
|
|
48
48
|
|
|
49
49
|
Single daemon per user. State lives under `~/.config/pinggy/` on Linux/macOS or `%APPDATA%/pinggy/` on Windows (helper: `src/utils/configDir.ts`):
|
|
50
50
|
|
|
51
|
-
- `daemon.json`: `{pid, port, startedAt}`.
|
|
51
|
+
- `daemon.json`: `{pid, port, startedAt, host, ipcVersion}`. `ipcVersion` is `IPC_VERSION` from `src/daemon/ipc/ipcRoutes.ts`. `ensureDaemonRunning()` refuses a live daemon whose version differs and tells the user to run `pinggy daemon stop`. Bump `IPC_VERSION` on any incompatible IPC or WebSocket change.
|
|
52
52
|
- `daemon-state.json`: detached tunnel configs for crash recovery (`src/daemon/stateStore.ts`). Deleted on clean shutdown; replayed on next start.
|
|
53
53
|
- `daemon.log`: SDK + daemon logs. CLI logs stay separate.
|
|
54
54
|
- `tunnels/<name>_<configId>.json`: saved tunnel configs from `pinggy config save`.
|
|
@@ -95,8 +95,12 @@ tsup bundles to `dist/` (CJS + ESM). tsc type-checks only (`npm run build:tsc`).
|
|
|
95
95
|
|
|
96
96
|
## English Style
|
|
97
97
|
|
|
98
|
-
- **No em-
|
|
98
|
+
- **Hyphen only.** The hyphen (-) is the only dash. No em-dash and no en-dash anywhere in any file. This includes numeric ranges (write 1-20) and empty-cell placeholders in tables. Use a period, a colon, or restructure the sentence instead.
|
|
99
|
+
- **No AI-sounding text.** Do not use words, phrases, or symbols typical of LLM output.
|
|
100
|
+
- **No emoji in the codebase.** Documentation may use one only if it is very apt.
|
|
101
|
+
- **Simple English.** Say things in a straightforward way. Keep every technical term and its exact meaning. Use simple but accurate technical terms, not jargon.
|
|
99
102
|
- **Short phrases.** Enough to convey technical meaning. Nothing more.
|
|
103
|
+
- **No metaphor or personification** in technical statements. State the behavior literally. Write "a partial report is scored and enters the benchmark", never "a partial is a full citizen".
|
|
100
104
|
- **No filler words.** Cut: "in order to", "it is important to note", "please note that", "essentially", "basically", "simply".
|
|
101
105
|
- **No passive voice** unless the subject is unknown or irrelevant.
|
|
102
106
|
- **No nominalizations.** Prefer "detect" over "perform detection"; "configure" over "apply configuration".
|
|
@@ -110,3 +114,12 @@ tsup bundles to `dist/` (CJS + ESM). tsc type-checks only (`npm run build:tsc`).
|
|
|
110
114
|
## Code Style
|
|
111
115
|
- **No dead code.** Remove unused imports, functions, and variables immediately. `ruff` enforces this.
|
|
112
116
|
- **Small functions.** If a function needs a comment to explain its sections, split it.
|
|
117
|
+
|
|
118
|
+
## Answering Questions
|
|
119
|
+
|
|
120
|
+
- **A question is not a change request.** When asked a question, do not change code. Answer it.
|
|
121
|
+
|
|
122
|
+
## Commit Messages
|
|
123
|
+
|
|
124
|
+
- **Plain change description.** Write the message as a human author would: what changed and why.
|
|
125
|
+
- **No assistant attribution.** Do not mention Claude, AI, or any assistant. No `Co-Authored-By` assistant trailer, no "Generated with" line, no tool attribution.
|
package/README.md
CHANGED
|
@@ -287,7 +287,7 @@ These flags apply to the CLI invocation. For daemon-wide log level and per-tunne
|
|
|
287
287
|
### **AutoReconnect**
|
|
288
288
|
| Flag | Description |
|
|
289
289
|
|------|-------------|
|
|
290
|
-
| `--autoreconnect`, `-a` |
|
|
290
|
+
| `--no-autoreconnect`, `-a` | Disable automatic reconnection on failure. Enabled by default, and it keeps retrying until the tunnel is back (no attempt limit) |
|
|
291
291
|
|
|
292
292
|
---
|
|
293
293
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DaemonHost,
|
|
3
3
|
IPCClient,
|
|
4
|
+
IPC_VERSION,
|
|
4
5
|
SessionMode,
|
|
5
6
|
ShutdownStatus
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-I4HXBP5G.js";
|
|
7
8
|
import {
|
|
8
9
|
TunnelAlreadyRunningError,
|
|
9
10
|
TunnelManager,
|
|
@@ -289,6 +290,8 @@ function pinggyOptionsToTunnelConfigV1(opts, configStoredInCli) {
|
|
|
289
290
|
serverAddress: opts.serverAddress || "a.pinggy.io:443",
|
|
290
291
|
token: opts.token || "",
|
|
291
292
|
autoReconnect: opts.autoReconnect ?? true,
|
|
293
|
+
reconnectInterval: opts.reconnectInterval,
|
|
294
|
+
maxReconnectAttempts: opts.maxReconnectAttempts,
|
|
292
295
|
force: opts.force ?? false,
|
|
293
296
|
webDebugger: opts.webDebugger || "",
|
|
294
297
|
forwarding: opts.forwarding ? opts.forwarding : "",
|
|
@@ -1612,6 +1615,19 @@ function getDaemonInfo() {
|
|
|
1612
1615
|
function isDaemonRunning() {
|
|
1613
1616
|
return getDaemonInfo() !== null;
|
|
1614
1617
|
}
|
|
1618
|
+
function daemonIpcVersion(info) {
|
|
1619
|
+
return info.ipcVersion ?? 0;
|
|
1620
|
+
}
|
|
1621
|
+
function isIpcCompatible(info) {
|
|
1622
|
+
return daemonIpcVersion(info) === IPC_VERSION;
|
|
1623
|
+
}
|
|
1624
|
+
function ipcMismatchMessage(info) {
|
|
1625
|
+
const versions = `daemon IPC v${daemonIpcVersion(info)}, this CLI expects v${IPC_VERSION}`;
|
|
1626
|
+
if (info.host === DaemonHost.APP) {
|
|
1627
|
+
return `The Pinggy app is running a daemon from a different version (${versions}). Update or restart the Pinggy app.`;
|
|
1628
|
+
}
|
|
1629
|
+
return `A daemon from a different Pinggy CLI version is running (PID ${info.pid}; ${versions}). Run "pinggy daemon stop" and retry. This stops all running tunnels.`;
|
|
1630
|
+
}
|
|
1615
1631
|
function getDaemonSpawnArgs() {
|
|
1616
1632
|
return {
|
|
1617
1633
|
command: process.execPath,
|
|
@@ -1658,9 +1674,12 @@ async function startDaemon() {
|
|
|
1658
1674
|
}
|
|
1659
1675
|
async function ensureDaemonRunning() {
|
|
1660
1676
|
const existing = getDaemonInfo();
|
|
1661
|
-
if (existing)
|
|
1677
|
+
if (existing) {
|
|
1678
|
+
if (!isIpcCompatible(existing)) throw new Error(ipcMismatchMessage(existing));
|
|
1679
|
+
return existing;
|
|
1680
|
+
}
|
|
1662
1681
|
if (process.versions.electron) {
|
|
1663
|
-
const { runDaemonChild } = await import("./daemonChild-
|
|
1682
|
+
const { runDaemonChild } = await import("./daemonChild-ZPZK5F4Q.js");
|
|
1664
1683
|
const handle = await runDaemonChild({
|
|
1665
1684
|
installSignalHandlers: false,
|
|
1666
1685
|
exitOnFailure: false,
|
|
@@ -1671,7 +1690,8 @@ async function ensureDaemonRunning() {
|
|
|
1671
1690
|
pid: handle.pid,
|
|
1672
1691
|
port: handle.port,
|
|
1673
1692
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1674
|
-
host: DaemonHost.APP
|
|
1693
|
+
host: DaemonHost.APP,
|
|
1694
|
+
ipcVersion: IPC_VERSION
|
|
1675
1695
|
};
|
|
1676
1696
|
}
|
|
1677
1697
|
return startDaemon();
|
|
@@ -1682,7 +1702,7 @@ function getInProcessDaemonHandle() {
|
|
|
1682
1702
|
async function getActiveTunnelSummaries(origin = "app") {
|
|
1683
1703
|
const info = getDaemonInfo();
|
|
1684
1704
|
if (!info) return [];
|
|
1685
|
-
const { IPCClient: IPCClient2 } = await import("./ipcClient-
|
|
1705
|
+
const { IPCClient: IPCClient2 } = await import("./ipcClient-URHJKNEH.js");
|
|
1686
1706
|
const client = new IPCClient2(info.port, origin);
|
|
1687
1707
|
let tunnels;
|
|
1688
1708
|
try {
|
|
@@ -1729,7 +1749,7 @@ async function stopDaemon() {
|
|
|
1729
1749
|
if (!info) return { ok: false, error: "No daemon is running." };
|
|
1730
1750
|
let daemonErrors = [];
|
|
1731
1751
|
try {
|
|
1732
|
-
const { IPCClient: IPCClient2 } = await import("./ipcClient-
|
|
1752
|
+
const { IPCClient: IPCClient2 } = await import("./ipcClient-URHJKNEH.js");
|
|
1733
1753
|
const client = new IPCClient2(info.port);
|
|
1734
1754
|
const result = await client.shutdown();
|
|
1735
1755
|
logger.debug("Sent shutdown command to daemon", { result });
|
|
@@ -1884,6 +1904,9 @@ var WsStream = class {
|
|
|
1884
1904
|
subscriptionCount() {
|
|
1885
1905
|
return this.subscribedTunnels.size;
|
|
1886
1906
|
}
|
|
1907
|
+
isSubscribed(tunnelId) {
|
|
1908
|
+
return this.subscribedTunnels.has(tunnelId);
|
|
1909
|
+
}
|
|
1887
1910
|
/** Snapshot of current subscriptions, for the reconnect path to replay. */
|
|
1888
1911
|
snapshotSubscriptions() {
|
|
1889
1912
|
return Array.from(this.subscribedTunnels.entries());
|
|
@@ -1917,6 +1940,16 @@ var WsStream = class {
|
|
|
1917
1940
|
onDisconnect(cb) {
|
|
1918
1941
|
this.callbacks.disconnect.push(cb);
|
|
1919
1942
|
}
|
|
1943
|
+
// Removal replaces the array instead of splicing it, so a callback that
|
|
1944
|
+
// unregisters itself while handleMessage is iterating cannot make the
|
|
1945
|
+
// dispatch loop skip the next callback. The removal applies from the next
|
|
1946
|
+
// event on.
|
|
1947
|
+
offStats(cb) {
|
|
1948
|
+
this.callbacks.stats = this.callbacks.stats.filter((c) => c !== cb);
|
|
1949
|
+
}
|
|
1950
|
+
offDisconnect(cb) {
|
|
1951
|
+
this.callbacks.disconnect = this.callbacks.disconnect.filter((c) => c !== cb);
|
|
1952
|
+
}
|
|
1920
1953
|
onReconnecting(cb) {
|
|
1921
1954
|
this.callbacks.reconnecting.push(cb);
|
|
1922
1955
|
}
|
|
@@ -2125,6 +2158,12 @@ var DaemonHealth = class {
|
|
|
2125
2158
|
var TunnelClient = class _TunnelClient {
|
|
2126
2159
|
constructor(options = {}) {
|
|
2127
2160
|
this.ipc = null;
|
|
2161
|
+
// App-compat shim state: registered listeners by id, and the tunnels whose
|
|
2162
|
+
// stream subscription those registrations opened (as opposed to attach()).
|
|
2163
|
+
this.shimStatsListeners = /* @__PURE__ */ new Map();
|
|
2164
|
+
this.shimDisconnectListeners = /* @__PURE__ */ new Map();
|
|
2165
|
+
this.shimSubscribedTunnels = /* @__PURE__ */ new Set();
|
|
2166
|
+
this.shimListenerSeq = 0;
|
|
2128
2167
|
this.origin = options.origin ?? "cli";
|
|
2129
2168
|
this.stream = new WsStream(() => {
|
|
2130
2169
|
if (!this.ipc) throw new Error("TunnelClient not initialized. Call ensureDaemon() first.");
|
|
@@ -2272,6 +2311,12 @@ var TunnelClient = class _TunnelClient {
|
|
|
2272
2311
|
onDisconnect(cb) {
|
|
2273
2312
|
this.stream.onDisconnect(cb);
|
|
2274
2313
|
}
|
|
2314
|
+
offStats(cb) {
|
|
2315
|
+
this.stream.offStats(cb);
|
|
2316
|
+
}
|
|
2317
|
+
offDisconnect(cb) {
|
|
2318
|
+
this.stream.offDisconnect(cb);
|
|
2319
|
+
}
|
|
2275
2320
|
onReconnecting(cb) {
|
|
2276
2321
|
this.stream.onReconnecting(cb);
|
|
2277
2322
|
}
|
|
@@ -2309,24 +2354,73 @@ var TunnelClient = class _TunnelClient {
|
|
|
2309
2354
|
isDaemonLost() {
|
|
2310
2355
|
return this.health.isLost();
|
|
2311
2356
|
}
|
|
2312
|
-
// App-compat shims
|
|
2357
|
+
// App-compat shims. Each registration is scoped to ONE tunnel, returns an
|
|
2358
|
+
// id, and is undone by the matching unregister call. Before this, every
|
|
2359
|
+
// register appended a callback that saw every subscribed tunnel's events
|
|
2360
|
+
// and could never be removed, so a consumer that registered per page view
|
|
2361
|
+
// received each stats update N times, forever.
|
|
2362
|
+
//
|
|
2363
|
+
// The first registration for a tunnel opens its stream subscription and
|
|
2364
|
+
// the last unregister drops it with a plain unsubscribe. detach() is not
|
|
2365
|
+
// used here on purpose: it closes the client once nothing is subscribed,
|
|
2366
|
+
// which also stops the daemon-health heartbeat, and a stats consumer must
|
|
2367
|
+
// not have that side effect.
|
|
2313
2368
|
handleRegisterStatsListener(tunnelId, listener) {
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
}
|
|
2369
|
+
const wrapped = (id, stats) => {
|
|
2370
|
+
if (id === tunnelId) listener(id, stats);
|
|
2371
|
+
};
|
|
2372
|
+
const listenerId = this.nextShimListenerId("stats");
|
|
2373
|
+
this.shimStatsListeners.set(listenerId, { tunnelId, wrapped });
|
|
2374
|
+
this.stream.onStats(wrapped);
|
|
2375
|
+
this.subscribeForShim(tunnelId);
|
|
2376
|
+
return listenerId;
|
|
2377
|
+
}
|
|
2378
|
+
handleUnregisterStatsListener(_tunnelId, listenerId) {
|
|
2379
|
+
const entry = this.shimStatsListeners.get(listenerId);
|
|
2380
|
+
if (!entry) return;
|
|
2381
|
+
this.shimStatsListeners.delete(listenerId);
|
|
2382
|
+
this.stream.offStats(entry.wrapped);
|
|
2383
|
+
this.releaseShimSubscription(entry.tunnelId);
|
|
2317
2384
|
}
|
|
2318
2385
|
handleRegisterDisconnectListener(tunnelId, listener) {
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
}
|
|
2322
|
-
|
|
2323
|
-
|
|
2386
|
+
const wrapped = (id, error, messages) => {
|
|
2387
|
+
if (id === tunnelId) listener(id, error, messages);
|
|
2388
|
+
};
|
|
2389
|
+
const listenerId = this.nextShimListenerId("disconnect");
|
|
2390
|
+
this.shimDisconnectListeners.set(listenerId, { tunnelId, wrapped });
|
|
2391
|
+
this.stream.onDisconnect(wrapped);
|
|
2392
|
+
this.subscribeForShim(tunnelId);
|
|
2393
|
+
return listenerId;
|
|
2394
|
+
}
|
|
2395
|
+
handleUnregisterDisconnectListener(_tunnelId, listenerId) {
|
|
2396
|
+
const entry = this.shimDisconnectListeners.get(listenerId);
|
|
2397
|
+
if (!entry) return;
|
|
2398
|
+
this.shimDisconnectListeners.delete(listenerId);
|
|
2399
|
+
this.stream.offDisconnect(entry.wrapped);
|
|
2400
|
+
this.releaseShimSubscription(entry.tunnelId);
|
|
2324
2401
|
}
|
|
2325
2402
|
async handleGetTunnelStats(tunnelId) {
|
|
2326
2403
|
this.assertClient();
|
|
2327
2404
|
return this.ipc.getTunnelStats(tunnelId);
|
|
2328
2405
|
}
|
|
2329
2406
|
// Private
|
|
2407
|
+
nextShimListenerId(kind) {
|
|
2408
|
+
this.shimListenerSeq += 1;
|
|
2409
|
+
return `${kind}-${this.shimListenerSeq}`;
|
|
2410
|
+
}
|
|
2411
|
+
subscribeForShim(tunnelId) {
|
|
2412
|
+
if (!this.stream.isSubscribed(tunnelId)) this.shimSubscribedTunnels.add(tunnelId);
|
|
2413
|
+
this.attach(tunnelId, SessionMode.Detached).catch((err) => {
|
|
2414
|
+
logger.debug("Listener subscribe failed", { tunnelId, error: errorMessage(err) });
|
|
2415
|
+
});
|
|
2416
|
+
}
|
|
2417
|
+
releaseShimSubscription(tunnelId) {
|
|
2418
|
+
if (!this.shimSubscribedTunnels.has(tunnelId)) return;
|
|
2419
|
+
for (const entry of this.shimStatsListeners.values()) if (entry.tunnelId === tunnelId) return;
|
|
2420
|
+
for (const entry of this.shimDisconnectListeners.values()) if (entry.tunnelId === tunnelId) return;
|
|
2421
|
+
this.shimSubscribedTunnels.delete(tunnelId);
|
|
2422
|
+
this.stream.unsubscribe(tunnelId);
|
|
2423
|
+
}
|
|
2330
2424
|
assertClient() {
|
|
2331
2425
|
if (!this.ipc) {
|
|
2332
2426
|
throw new Error("TunnelClient not initialized. Call ensureDaemon() first.");
|
|
@@ -2351,6 +2445,9 @@ export {
|
|
|
2351
2445
|
getRemoteManagementState,
|
|
2352
2446
|
getDaemonInfo,
|
|
2353
2447
|
isDaemonRunning,
|
|
2448
|
+
daemonIpcVersion,
|
|
2449
|
+
isIpcCompatible,
|
|
2450
|
+
ipcMismatchMessage,
|
|
2354
2451
|
startDaemon,
|
|
2355
2452
|
ensureDaemonRunning,
|
|
2356
2453
|
getInProcessDaemonHandle,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import http from "http";
|
|
3
3
|
|
|
4
4
|
// src/daemon/ipc/ipcRoutes.ts
|
|
5
|
+
var IPC_VERSION = 1;
|
|
5
6
|
var Route = {
|
|
6
7
|
Ping: "GET /ping",
|
|
7
8
|
ListTunnels: "GET /tunnels",
|
|
@@ -166,6 +167,7 @@ var IPCClient = class {
|
|
|
166
167
|
};
|
|
167
168
|
|
|
168
169
|
export {
|
|
170
|
+
IPC_VERSION,
|
|
169
171
|
Route,
|
|
170
172
|
DaemonHost,
|
|
171
173
|
ShutdownStatus,
|
|
@@ -3,23 +3,27 @@ import {
|
|
|
3
3
|
TunnelClient,
|
|
4
4
|
TunnelOperations,
|
|
5
5
|
buildRemoteManagementWsUrl,
|
|
6
|
+
daemonIpcVersion,
|
|
6
7
|
getDaemonInfo,
|
|
7
8
|
initiateRemoteManagement,
|
|
9
|
+
ipcMismatchMessage,
|
|
8
10
|
isDaemonRunning,
|
|
9
11
|
isErrorResponse,
|
|
12
|
+
isIpcCompatible,
|
|
10
13
|
startDaemon,
|
|
11
14
|
startRemoteManagement,
|
|
12
15
|
stopDaemon
|
|
13
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-B6IKYBLU.js";
|
|
14
17
|
import {
|
|
15
18
|
readDaemonConfig
|
|
16
19
|
} from "./chunk-MT44NAXX.js";
|
|
17
20
|
import {
|
|
18
21
|
DaemonHost,
|
|
22
|
+
IPC_VERSION,
|
|
19
23
|
Route,
|
|
20
24
|
SessionMode,
|
|
21
25
|
ShutdownStatus
|
|
22
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-I4HXBP5G.js";
|
|
23
27
|
import {
|
|
24
28
|
ConfigVerb,
|
|
25
29
|
SUBCOMMANDS,
|
|
@@ -271,7 +275,10 @@ var defaultOptions = {
|
|
|
271
275
|
originalRequestUrl: false,
|
|
272
276
|
allowPreflight: false,
|
|
273
277
|
reverseProxy: true,
|
|
274
|
-
autoReconnect: true
|
|
278
|
+
autoReconnect: true,
|
|
279
|
+
// 0 = never give up: keep retrying every reconnectInterval until the tunnel
|
|
280
|
+
// is back. Not user-configurable from the CLI on purpose.
|
|
281
|
+
maxReconnectAttempts: 0
|
|
275
282
|
};
|
|
276
283
|
|
|
277
284
|
// src/cli/extendedOptions.ts
|
|
@@ -879,7 +886,9 @@ function buildFinalConfig(values, positionals, baseConfig) {
|
|
|
879
886
|
token: token || (configFromFile?.token || (typeof values.token === "string" ? values.token : "")),
|
|
880
887
|
serverAddress: server ? removeIPv6Brackets(server) : configFromFile?.serverAddress || defaultOptions.serverAddress,
|
|
881
888
|
isQRCode: qrCode || (configFromFile?.isQRCode || false),
|
|
882
|
-
|
|
889
|
+
// `??`, not a truthiness check: a saved `autoReconnect: false` must not
|
|
890
|
+
// flip back to the default.
|
|
891
|
+
autoReconnect: configFromFile?.autoReconnect ?? defaultOptions.autoReconnect,
|
|
883
892
|
optional: {
|
|
884
893
|
serve: configFromFile?.optional?.serve || void 0,
|
|
885
894
|
noTui: values.noTui || values.notui || hasRemoteManagement(values) || (configFromFile?.optional?.noTui || false)
|
|
@@ -1779,9 +1788,13 @@ async function handleDaemon(args) {
|
|
|
1779
1788
|
}
|
|
1780
1789
|
}
|
|
1781
1790
|
async function handleDaemonStart() {
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1791
|
+
const info = getDaemonInfo();
|
|
1792
|
+
if (info) {
|
|
1793
|
+
if (!isIpcCompatible(info)) {
|
|
1794
|
+
printer_default.error(ipcMismatchMessage(info));
|
|
1795
|
+
process.exit(1);
|
|
1796
|
+
}
|
|
1797
|
+
printer_default.print(pico2.yellow(`Daemon already running (PID ${info.pid}, port ${info.port}). Use: pinggy daemon status for details.`));
|
|
1785
1798
|
return;
|
|
1786
1799
|
}
|
|
1787
1800
|
const autoConfigs = getAutoStartConfigs();
|
|
@@ -1794,8 +1807,8 @@ async function handleDaemonStart() {
|
|
|
1794
1807
|
printer_default.print(pico2.cyanBright("Starting daemon..."));
|
|
1795
1808
|
}
|
|
1796
1809
|
try {
|
|
1797
|
-
const
|
|
1798
|
-
printer_default.success(`Daemon started PID ${
|
|
1810
|
+
const info2 = await startDaemon();
|
|
1811
|
+
printer_default.success(`Daemon started PID ${info2.pid}.`);
|
|
1799
1812
|
} catch (err) {
|
|
1800
1813
|
printer_default.error(`Failed to start daemon: ${errorMessage(err)}`);
|
|
1801
1814
|
process.exit(1);
|
|
@@ -1832,6 +1845,10 @@ function handleDaemonStatus() {
|
|
|
1832
1845
|
printer_default.print(` Started: ${info.startedAt}`);
|
|
1833
1846
|
printer_default.print(` Uptime: ${uptimeStr}`);
|
|
1834
1847
|
printer_default.print(` Host: ${info.host === DaemonHost.APP ? "Pinggy app (in-process)" : "standalone (CLI)"}`);
|
|
1848
|
+
printer_default.print(` IPC: v${daemonIpcVersion(info)}`);
|
|
1849
|
+
if (!isIpcCompatible(info)) {
|
|
1850
|
+
printer_default.print(pico2.yellow(` Warning: ${ipcMismatchMessage(info)}`));
|
|
1851
|
+
}
|
|
1835
1852
|
}
|
|
1836
1853
|
|
|
1837
1854
|
// src/cli/subcommand/handlers/psCommand.ts
|
|
@@ -2610,7 +2627,8 @@ var IPCServer = class {
|
|
|
2610
2627
|
[Route.Ping]: () => ({
|
|
2611
2628
|
status: "ok",
|
|
2612
2629
|
pid: process.pid,
|
|
2613
|
-
uptime: Math.floor((Date.now() - this.startedAt) / 1e3)
|
|
2630
|
+
uptime: Math.floor((Date.now() - this.startedAt) / 1e3),
|
|
2631
|
+
ipcVersion: IPC_VERSION
|
|
2614
2632
|
}),
|
|
2615
2633
|
[Route.ListTunnels]: async () => {
|
|
2616
2634
|
const res = await this.ops.handleListV2();
|
|
@@ -3403,7 +3421,8 @@ async function runDaemonChild(opts = {}) {
|
|
|
3403
3421
|
pid: process.pid,
|
|
3404
3422
|
port,
|
|
3405
3423
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3406
|
-
host: daemonHost
|
|
3424
|
+
host: daemonHost,
|
|
3425
|
+
ipcVersion: IPC_VERSION
|
|
3407
3426
|
};
|
|
3408
3427
|
writeDaemonInfo(info);
|
|
3409
3428
|
logger.info("Daemon info written", info);
|
|
@@ -3444,7 +3463,7 @@ async function main() {
|
|
|
3444
3463
|
const { values, positionals, hasAnyArgs } = parseCliArgs(cliOptions);
|
|
3445
3464
|
configureLogger(values);
|
|
3446
3465
|
if (values["_daemon-child"]) {
|
|
3447
|
-
const { runDaemonChild: runDaemonChild2 } = await import("./daemonChild-
|
|
3466
|
+
const { runDaemonChild: runDaemonChild2 } = await import("./daemonChild-ZPZK5F4Q.js");
|
|
3448
3467
|
await runDaemonChild2();
|
|
3449
3468
|
return;
|
|
3450
3469
|
}
|
|
@@ -6,12 +6,12 @@ import {
|
|
|
6
6
|
trackIPCTunnelStart,
|
|
7
7
|
trackTunnelStart,
|
|
8
8
|
trackTunnelStop
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-P7XRY5N4.js";
|
|
10
|
+
import "./chunk-B6IKYBLU.js";
|
|
11
11
|
import "./chunk-MT44NAXX.js";
|
|
12
12
|
import {
|
|
13
13
|
DaemonHost
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-I4HXBP5G.js";
|
|
15
15
|
import "./chunk-YWXADADB.js";
|
|
16
16
|
import "./chunk-PQSLSIDA.js";
|
|
17
17
|
import "./chunk-UB26QJ4T.js";
|
package/dist/index.cjs
CHANGED
|
@@ -2194,6 +2194,8 @@ function pinggyOptionsToTunnelConfigV1(opts, configStoredInCli) {
|
|
|
2194
2194
|
serverAddress: opts.serverAddress || "a.pinggy.io:443",
|
|
2195
2195
|
token: opts.token || "",
|
|
2196
2196
|
autoReconnect: opts.autoReconnect ?? true,
|
|
2197
|
+
reconnectInterval: opts.reconnectInterval,
|
|
2198
|
+
maxReconnectAttempts: opts.maxReconnectAttempts,
|
|
2197
2199
|
force: opts.force ?? false,
|
|
2198
2200
|
webDebugger: opts.webDebugger || "",
|
|
2199
2201
|
forwarding: opts.forwarding ? opts.forwarding : "",
|
|
@@ -2791,11 +2793,12 @@ var init_handler = __esm({
|
|
|
2791
2793
|
});
|
|
2792
2794
|
|
|
2793
2795
|
// src/daemon/ipc/ipcRoutes.ts
|
|
2794
|
-
var Route, DaemonHost, ShutdownStatus, SessionMode;
|
|
2796
|
+
var IPC_VERSION, Route, DaemonHost, ShutdownStatus, SessionMode;
|
|
2795
2797
|
var init_ipcRoutes = __esm({
|
|
2796
2798
|
"src/daemon/ipc/ipcRoutes.ts"() {
|
|
2797
2799
|
"use strict";
|
|
2798
2800
|
init_cjs_shims();
|
|
2801
|
+
IPC_VERSION = 1;
|
|
2799
2802
|
Route = {
|
|
2800
2803
|
Ping: "GET /ping",
|
|
2801
2804
|
ListTunnels: "GET /tunnels",
|
|
@@ -4093,7 +4096,10 @@ var init_defaults = __esm({
|
|
|
4093
4096
|
originalRequestUrl: false,
|
|
4094
4097
|
allowPreflight: false,
|
|
4095
4098
|
reverseProxy: true,
|
|
4096
|
-
autoReconnect: true
|
|
4099
|
+
autoReconnect: true,
|
|
4100
|
+
// 0 = never give up: keep retrying every reconnectInterval until the tunnel
|
|
4101
|
+
// is back. Not user-configurable from the CLI on purpose.
|
|
4102
|
+
maxReconnectAttempts: 0
|
|
4097
4103
|
};
|
|
4098
4104
|
}
|
|
4099
4105
|
});
|
|
@@ -4697,7 +4703,9 @@ function buildFinalConfig(values, positionals, baseConfig) {
|
|
|
4697
4703
|
token: token || (configFromFile?.token || (typeof values.token === "string" ? values.token : "")),
|
|
4698
4704
|
serverAddress: server ? removeIPv6Brackets(server) : configFromFile?.serverAddress || defaultOptions.serverAddress,
|
|
4699
4705
|
isQRCode: qrCode || (configFromFile?.isQRCode || false),
|
|
4700
|
-
|
|
4706
|
+
// `??`, not a truthiness check: a saved `autoReconnect: false` must not
|
|
4707
|
+
// flip back to the default.
|
|
4708
|
+
autoReconnect: configFromFile?.autoReconnect ?? defaultOptions.autoReconnect,
|
|
4701
4709
|
optional: {
|
|
4702
4710
|
serve: configFromFile?.optional?.serve || void 0,
|
|
4703
4711
|
noTui: values.noTui || values.notui || hasRemoteManagement(values) || (configFromFile?.optional?.noTui || false)
|
|
@@ -7527,9 +7535,13 @@ async function handleDaemon(args) {
|
|
|
7527
7535
|
}
|
|
7528
7536
|
}
|
|
7529
7537
|
async function handleDaemonStart() {
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7538
|
+
const info = getDaemonInfo();
|
|
7539
|
+
if (info) {
|
|
7540
|
+
if (!isIpcCompatible(info)) {
|
|
7541
|
+
printer_default.error(ipcMismatchMessage(info));
|
|
7542
|
+
process.exit(1);
|
|
7543
|
+
}
|
|
7544
|
+
printer_default.print(import_picocolors6.default.yellow(`Daemon already running (PID ${info.pid}, port ${info.port}). Use: pinggy daemon status for details.`));
|
|
7533
7545
|
return;
|
|
7534
7546
|
}
|
|
7535
7547
|
const autoConfigs = getAutoStartConfigs();
|
|
@@ -7542,8 +7554,8 @@ async function handleDaemonStart() {
|
|
|
7542
7554
|
printer_default.print(import_picocolors6.default.cyanBright("Starting daemon..."));
|
|
7543
7555
|
}
|
|
7544
7556
|
try {
|
|
7545
|
-
const
|
|
7546
|
-
printer_default.success(`Daemon started PID ${
|
|
7557
|
+
const info2 = await startDaemon();
|
|
7558
|
+
printer_default.success(`Daemon started PID ${info2.pid}.`);
|
|
7547
7559
|
} catch (err) {
|
|
7548
7560
|
printer_default.error(`Failed to start daemon: ${errorMessage(err)}`);
|
|
7549
7561
|
process.exit(1);
|
|
@@ -7580,6 +7592,10 @@ function handleDaemonStatus() {
|
|
|
7580
7592
|
printer_default.print(` Started: ${info.startedAt}`);
|
|
7581
7593
|
printer_default.print(` Uptime: ${uptimeStr}`);
|
|
7582
7594
|
printer_default.print(` Host: ${info.host === DaemonHost.APP ? "Pinggy app (in-process)" : "standalone (CLI)"}`);
|
|
7595
|
+
printer_default.print(` IPC: v${daemonIpcVersion(info)}`);
|
|
7596
|
+
if (!isIpcCompatible(info)) {
|
|
7597
|
+
printer_default.print(import_picocolors6.default.yellow(` Warning: ${ipcMismatchMessage(info)}`));
|
|
7598
|
+
}
|
|
7583
7599
|
}
|
|
7584
7600
|
var import_picocolors6;
|
|
7585
7601
|
var init_daemonCommandsHandler = __esm({
|
|
@@ -8573,7 +8589,8 @@ var init_ipcServer = __esm({
|
|
|
8573
8589
|
[Route.Ping]: () => ({
|
|
8574
8590
|
status: "ok",
|
|
8575
8591
|
pid: process.pid,
|
|
8576
|
-
uptime: Math.floor((Date.now() - this.startedAt) / 1e3)
|
|
8592
|
+
uptime: Math.floor((Date.now() - this.startedAt) / 1e3),
|
|
8593
|
+
ipcVersion: IPC_VERSION
|
|
8577
8594
|
}),
|
|
8578
8595
|
[Route.ListTunnels]: async () => {
|
|
8579
8596
|
const res = await this.ops.handleListV2();
|
|
@@ -9388,7 +9405,8 @@ async function runDaemonChild(opts = {}) {
|
|
|
9388
9405
|
pid: process.pid,
|
|
9389
9406
|
port,
|
|
9390
9407
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9391
|
-
host: daemonHost
|
|
9408
|
+
host: daemonHost,
|
|
9409
|
+
ipcVersion: IPC_VERSION
|
|
9392
9410
|
};
|
|
9393
9411
|
writeDaemonInfo(info);
|
|
9394
9412
|
logger.info("Daemon info written", info);
|
|
@@ -9475,6 +9493,19 @@ function getDaemonInfo() {
|
|
|
9475
9493
|
function isDaemonRunning() {
|
|
9476
9494
|
return getDaemonInfo() !== null;
|
|
9477
9495
|
}
|
|
9496
|
+
function daemonIpcVersion(info) {
|
|
9497
|
+
return info.ipcVersion ?? 0;
|
|
9498
|
+
}
|
|
9499
|
+
function isIpcCompatible(info) {
|
|
9500
|
+
return daemonIpcVersion(info) === IPC_VERSION;
|
|
9501
|
+
}
|
|
9502
|
+
function ipcMismatchMessage(info) {
|
|
9503
|
+
const versions = `daemon IPC v${daemonIpcVersion(info)}, this CLI expects v${IPC_VERSION}`;
|
|
9504
|
+
if (info.host === DaemonHost.APP) {
|
|
9505
|
+
return `The Pinggy app is running a daemon from a different version (${versions}). Update or restart the Pinggy app.`;
|
|
9506
|
+
}
|
|
9507
|
+
return `A daemon from a different Pinggy CLI version is running (PID ${info.pid}; ${versions}). Run "pinggy daemon stop" and retry. This stops all running tunnels.`;
|
|
9508
|
+
}
|
|
9478
9509
|
function getDaemonSpawnArgs() {
|
|
9479
9510
|
return {
|
|
9480
9511
|
command: process.execPath,
|
|
@@ -9521,7 +9552,10 @@ async function startDaemon() {
|
|
|
9521
9552
|
}
|
|
9522
9553
|
async function ensureDaemonRunning() {
|
|
9523
9554
|
const existing = getDaemonInfo();
|
|
9524
|
-
if (existing)
|
|
9555
|
+
if (existing) {
|
|
9556
|
+
if (!isIpcCompatible(existing)) throw new Error(ipcMismatchMessage(existing));
|
|
9557
|
+
return existing;
|
|
9558
|
+
}
|
|
9525
9559
|
if (process.versions.electron) {
|
|
9526
9560
|
const { runDaemonChild: runDaemonChild2 } = await Promise.resolve().then(() => (init_daemonChild(), daemonChild_exports));
|
|
9527
9561
|
const handle = await runDaemonChild2({
|
|
@@ -9534,7 +9568,8 @@ async function ensureDaemonRunning() {
|
|
|
9534
9568
|
pid: handle.pid,
|
|
9535
9569
|
port: handle.port,
|
|
9536
9570
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9537
|
-
host: DaemonHost.APP
|
|
9571
|
+
host: DaemonHost.APP,
|
|
9572
|
+
ipcVersion: IPC_VERSION
|
|
9538
9573
|
};
|
|
9539
9574
|
}
|
|
9540
9575
|
return startDaemon();
|
|
@@ -9773,6 +9808,9 @@ var init_wsStream = __esm({
|
|
|
9773
9808
|
subscriptionCount() {
|
|
9774
9809
|
return this.subscribedTunnels.size;
|
|
9775
9810
|
}
|
|
9811
|
+
isSubscribed(tunnelId) {
|
|
9812
|
+
return this.subscribedTunnels.has(tunnelId);
|
|
9813
|
+
}
|
|
9776
9814
|
/** Snapshot of current subscriptions, for the reconnect path to replay. */
|
|
9777
9815
|
snapshotSubscriptions() {
|
|
9778
9816
|
return Array.from(this.subscribedTunnels.entries());
|
|
@@ -9806,6 +9844,16 @@ var init_wsStream = __esm({
|
|
|
9806
9844
|
onDisconnect(cb) {
|
|
9807
9845
|
this.callbacks.disconnect.push(cb);
|
|
9808
9846
|
}
|
|
9847
|
+
// Removal replaces the array instead of splicing it, so a callback that
|
|
9848
|
+
// unregisters itself while handleMessage is iterating cannot make the
|
|
9849
|
+
// dispatch loop skip the next callback. The removal applies from the next
|
|
9850
|
+
// event on.
|
|
9851
|
+
offStats(cb) {
|
|
9852
|
+
this.callbacks.stats = this.callbacks.stats.filter((c) => c !== cb);
|
|
9853
|
+
}
|
|
9854
|
+
offDisconnect(cb) {
|
|
9855
|
+
this.callbacks.disconnect = this.callbacks.disconnect.filter((c) => c !== cb);
|
|
9856
|
+
}
|
|
9809
9857
|
onReconnecting(cb) {
|
|
9810
9858
|
this.callbacks.reconnecting.push(cb);
|
|
9811
9859
|
}
|
|
@@ -10040,6 +10088,8 @@ var init_tunnelClient = __esm({
|
|
|
10040
10088
|
init_cjs_shims();
|
|
10041
10089
|
init_ipcClient();
|
|
10042
10090
|
init_daemonManager();
|
|
10091
|
+
init_logger();
|
|
10092
|
+
init_util();
|
|
10043
10093
|
init_wsStream();
|
|
10044
10094
|
init_daemonHealth();
|
|
10045
10095
|
init_daemonTunnelHandler();
|
|
@@ -10047,6 +10097,12 @@ var init_tunnelClient = __esm({
|
|
|
10047
10097
|
TunnelClient = class _TunnelClient {
|
|
10048
10098
|
constructor(options = {}) {
|
|
10049
10099
|
this.ipc = null;
|
|
10100
|
+
// App-compat shim state: registered listeners by id, and the tunnels whose
|
|
10101
|
+
// stream subscription those registrations opened (as opposed to attach()).
|
|
10102
|
+
this.shimStatsListeners = /* @__PURE__ */ new Map();
|
|
10103
|
+
this.shimDisconnectListeners = /* @__PURE__ */ new Map();
|
|
10104
|
+
this.shimSubscribedTunnels = /* @__PURE__ */ new Set();
|
|
10105
|
+
this.shimListenerSeq = 0;
|
|
10050
10106
|
this.origin = options.origin ?? "cli";
|
|
10051
10107
|
this.stream = new WsStream(() => {
|
|
10052
10108
|
if (!this.ipc) throw new Error("TunnelClient not initialized. Call ensureDaemon() first.");
|
|
@@ -10194,6 +10250,12 @@ var init_tunnelClient = __esm({
|
|
|
10194
10250
|
onDisconnect(cb) {
|
|
10195
10251
|
this.stream.onDisconnect(cb);
|
|
10196
10252
|
}
|
|
10253
|
+
offStats(cb) {
|
|
10254
|
+
this.stream.offStats(cb);
|
|
10255
|
+
}
|
|
10256
|
+
offDisconnect(cb) {
|
|
10257
|
+
this.stream.offDisconnect(cb);
|
|
10258
|
+
}
|
|
10197
10259
|
onReconnecting(cb) {
|
|
10198
10260
|
this.stream.onReconnecting(cb);
|
|
10199
10261
|
}
|
|
@@ -10231,24 +10293,73 @@ var init_tunnelClient = __esm({
|
|
|
10231
10293
|
isDaemonLost() {
|
|
10232
10294
|
return this.health.isLost();
|
|
10233
10295
|
}
|
|
10234
|
-
// App-compat shims
|
|
10296
|
+
// App-compat shims. Each registration is scoped to ONE tunnel, returns an
|
|
10297
|
+
// id, and is undone by the matching unregister call. Before this, every
|
|
10298
|
+
// register appended a callback that saw every subscribed tunnel's events
|
|
10299
|
+
// and could never be removed, so a consumer that registered per page view
|
|
10300
|
+
// received each stats update N times, forever.
|
|
10301
|
+
//
|
|
10302
|
+
// The first registration for a tunnel opens its stream subscription and
|
|
10303
|
+
// the last unregister drops it with a plain unsubscribe. detach() is not
|
|
10304
|
+
// used here on purpose: it closes the client once nothing is subscribed,
|
|
10305
|
+
// which also stops the daemon-health heartbeat, and a stats consumer must
|
|
10306
|
+
// not have that side effect.
|
|
10235
10307
|
handleRegisterStatsListener(tunnelId, listener) {
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
}
|
|
10308
|
+
const wrapped = (id, stats) => {
|
|
10309
|
+
if (id === tunnelId) listener(id, stats);
|
|
10310
|
+
};
|
|
10311
|
+
const listenerId = this.nextShimListenerId("stats");
|
|
10312
|
+
this.shimStatsListeners.set(listenerId, { tunnelId, wrapped });
|
|
10313
|
+
this.stream.onStats(wrapped);
|
|
10314
|
+
this.subscribeForShim(tunnelId);
|
|
10315
|
+
return listenerId;
|
|
10316
|
+
}
|
|
10317
|
+
handleUnregisterStatsListener(_tunnelId, listenerId) {
|
|
10318
|
+
const entry = this.shimStatsListeners.get(listenerId);
|
|
10319
|
+
if (!entry) return;
|
|
10320
|
+
this.shimStatsListeners.delete(listenerId);
|
|
10321
|
+
this.stream.offStats(entry.wrapped);
|
|
10322
|
+
this.releaseShimSubscription(entry.tunnelId);
|
|
10239
10323
|
}
|
|
10240
10324
|
handleRegisterDisconnectListener(tunnelId, listener) {
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
}
|
|
10244
|
-
|
|
10245
|
-
|
|
10325
|
+
const wrapped = (id, error, messages) => {
|
|
10326
|
+
if (id === tunnelId) listener(id, error, messages);
|
|
10327
|
+
};
|
|
10328
|
+
const listenerId = this.nextShimListenerId("disconnect");
|
|
10329
|
+
this.shimDisconnectListeners.set(listenerId, { tunnelId, wrapped });
|
|
10330
|
+
this.stream.onDisconnect(wrapped);
|
|
10331
|
+
this.subscribeForShim(tunnelId);
|
|
10332
|
+
return listenerId;
|
|
10333
|
+
}
|
|
10334
|
+
handleUnregisterDisconnectListener(_tunnelId, listenerId) {
|
|
10335
|
+
const entry = this.shimDisconnectListeners.get(listenerId);
|
|
10336
|
+
if (!entry) return;
|
|
10337
|
+
this.shimDisconnectListeners.delete(listenerId);
|
|
10338
|
+
this.stream.offDisconnect(entry.wrapped);
|
|
10339
|
+
this.releaseShimSubscription(entry.tunnelId);
|
|
10246
10340
|
}
|
|
10247
10341
|
async handleGetTunnelStats(tunnelId) {
|
|
10248
10342
|
this.assertClient();
|
|
10249
10343
|
return this.ipc.getTunnelStats(tunnelId);
|
|
10250
10344
|
}
|
|
10251
10345
|
// Private
|
|
10346
|
+
nextShimListenerId(kind) {
|
|
10347
|
+
this.shimListenerSeq += 1;
|
|
10348
|
+
return `${kind}-${this.shimListenerSeq}`;
|
|
10349
|
+
}
|
|
10350
|
+
subscribeForShim(tunnelId) {
|
|
10351
|
+
if (!this.stream.isSubscribed(tunnelId)) this.shimSubscribedTunnels.add(tunnelId);
|
|
10352
|
+
this.attach(tunnelId, SessionMode.Detached).catch((err) => {
|
|
10353
|
+
logger.debug("Listener subscribe failed", { tunnelId, error: errorMessage(err) });
|
|
10354
|
+
});
|
|
10355
|
+
}
|
|
10356
|
+
releaseShimSubscription(tunnelId) {
|
|
10357
|
+
if (!this.shimSubscribedTunnels.has(tunnelId)) return;
|
|
10358
|
+
for (const entry of this.shimStatsListeners.values()) if (entry.tunnelId === tunnelId) return;
|
|
10359
|
+
for (const entry of this.shimDisconnectListeners.values()) if (entry.tunnelId === tunnelId) return;
|
|
10360
|
+
this.shimSubscribedTunnels.delete(tunnelId);
|
|
10361
|
+
this.stream.unsubscribe(tunnelId);
|
|
10362
|
+
}
|
|
10252
10363
|
assertClient() {
|
|
10253
10364
|
if (!this.ipc) {
|
|
10254
10365
|
throw new Error("TunnelClient not initialized. Call ensureDaemon() first.");
|
package/dist/index.d.cts
CHANGED
|
@@ -658,6 +658,7 @@ interface PingResponse {
|
|
|
658
658
|
status: string;
|
|
659
659
|
pid: number;
|
|
660
660
|
uptime: number;
|
|
661
|
+
ipcVersion: number;
|
|
661
662
|
}
|
|
662
663
|
type ListTunnelsResponse = (TunnelResponseV2 & {
|
|
663
664
|
mode?: SessionMode;
|
|
@@ -991,6 +992,7 @@ declare class WsStream {
|
|
|
991
992
|
unsubscribe(tunnelId: string): boolean;
|
|
992
993
|
hasSubscriptions(): boolean;
|
|
993
994
|
subscriptionCount(): number;
|
|
995
|
+
isSubscribed(tunnelId: string): boolean;
|
|
994
996
|
/** Snapshot of current subscriptions, for the reconnect path to replay. */
|
|
995
997
|
snapshotSubscriptions(): Array<[string, SubscriptionInfo]>;
|
|
996
998
|
/**
|
|
@@ -1002,6 +1004,8 @@ declare class WsStream {
|
|
|
1002
1004
|
onClose(cb: (code: number) => void): void;
|
|
1003
1005
|
onStats(cb: StatsCallback): void;
|
|
1004
1006
|
onDisconnect(cb: DisconnectCallback): void;
|
|
1007
|
+
offStats(cb: StatsCallback): void;
|
|
1008
|
+
offDisconnect(cb: DisconnectCallback): void;
|
|
1005
1009
|
onReconnecting(cb: ReconnectingCallback): void;
|
|
1006
1010
|
onReconnected(cb: ReconnectedCallback): void;
|
|
1007
1011
|
onReconnectionFailed(cb: ReconnectionFailedCallback): void;
|
|
@@ -1078,6 +1082,10 @@ declare class TunnelClient {
|
|
|
1078
1082
|
private origin;
|
|
1079
1083
|
private stream;
|
|
1080
1084
|
health: DaemonHealth;
|
|
1085
|
+
private shimStatsListeners;
|
|
1086
|
+
private shimDisconnectListeners;
|
|
1087
|
+
private shimSubscribedTunnels;
|
|
1088
|
+
private shimListenerSeq;
|
|
1081
1089
|
constructor(options?: TunnelClientOptions);
|
|
1082
1090
|
ensureDaemon(): Promise<void>;
|
|
1083
1091
|
static forRemoteManagement(): Promise<TunnelClient>;
|
|
@@ -1085,11 +1093,7 @@ declare class TunnelClient {
|
|
|
1085
1093
|
* Close the WebSocket connection and cleanup.
|
|
1086
1094
|
*/
|
|
1087
1095
|
close(): void;
|
|
1088
|
-
ping(): Promise<
|
|
1089
|
-
status: string;
|
|
1090
|
-
pid: number;
|
|
1091
|
-
uptime: number;
|
|
1092
|
-
}>;
|
|
1096
|
+
ping(): Promise<PingResponse>;
|
|
1093
1097
|
handleStartV2(config: object, noWait?: boolean, mode?: SessionMode): Promise<TunnelResponseV2 | ErrorResponse>;
|
|
1094
1098
|
handleStart(config: object, noWait?: boolean, mode?: SessionMode): Promise<TunnelResponse | ErrorResponse>;
|
|
1095
1099
|
handleUpdateConfig(config: object, noWait?: boolean): Promise<TunnelResponse | ErrorResponse>;
|
|
@@ -1119,6 +1123,8 @@ declare class TunnelClient {
|
|
|
1119
1123
|
detach(tunnelId: string): void;
|
|
1120
1124
|
onStats(cb: StatsCallback): void;
|
|
1121
1125
|
onDisconnect(cb: DisconnectCallback): void;
|
|
1126
|
+
offStats(cb: StatsCallback): void;
|
|
1127
|
+
offDisconnect(cb: DisconnectCallback): void;
|
|
1122
1128
|
onReconnecting(cb: ReconnectingCallback): void;
|
|
1123
1129
|
onReconnected(cb: ReconnectedCallback): void;
|
|
1124
1130
|
onReconnectionFailed(cb: ReconnectionFailedCallback): void;
|
|
@@ -1131,10 +1137,14 @@ declare class TunnelClient {
|
|
|
1131
1137
|
onDaemonReconnecting(cb: DaemonReconnectingCallback): void;
|
|
1132
1138
|
onDaemonReconnected(cb: DaemonReconnectedCallback): void;
|
|
1133
1139
|
isDaemonLost(): boolean;
|
|
1134
|
-
handleRegisterStatsListener(tunnelId: string, listener:
|
|
1135
|
-
|
|
1136
|
-
|
|
1140
|
+
handleRegisterStatsListener(tunnelId: string, listener: StatsCallback): string;
|
|
1141
|
+
handleUnregisterStatsListener(_tunnelId: string, listenerId: string): void;
|
|
1142
|
+
handleRegisterDisconnectListener(tunnelId: string, listener: DisconnectCallback): string;
|
|
1143
|
+
handleUnregisterDisconnectListener(_tunnelId: string, listenerId: string): void;
|
|
1137
1144
|
handleGetTunnelStats(tunnelId: string): Promise<TunnelUsageType[] | ErrorResponse>;
|
|
1145
|
+
private nextShimListenerId;
|
|
1146
|
+
private subscribeForShim;
|
|
1147
|
+
private releaseShimSubscription;
|
|
1138
1148
|
private assertClient;
|
|
1139
1149
|
}
|
|
1140
1150
|
|
|
@@ -1143,6 +1153,8 @@ interface DaemonInfo {
|
|
|
1143
1153
|
port: number;
|
|
1144
1154
|
startedAt: string;
|
|
1145
1155
|
host?: DaemonHost;
|
|
1156
|
+
/** IPC protocol version the daemon speaks. Absent in daemon.json written by older builds. */
|
|
1157
|
+
ipcVersion?: number;
|
|
1146
1158
|
}
|
|
1147
1159
|
|
|
1148
1160
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -658,6 +658,7 @@ interface PingResponse {
|
|
|
658
658
|
status: string;
|
|
659
659
|
pid: number;
|
|
660
660
|
uptime: number;
|
|
661
|
+
ipcVersion: number;
|
|
661
662
|
}
|
|
662
663
|
type ListTunnelsResponse = (TunnelResponseV2 & {
|
|
663
664
|
mode?: SessionMode;
|
|
@@ -991,6 +992,7 @@ declare class WsStream {
|
|
|
991
992
|
unsubscribe(tunnelId: string): boolean;
|
|
992
993
|
hasSubscriptions(): boolean;
|
|
993
994
|
subscriptionCount(): number;
|
|
995
|
+
isSubscribed(tunnelId: string): boolean;
|
|
994
996
|
/** Snapshot of current subscriptions, for the reconnect path to replay. */
|
|
995
997
|
snapshotSubscriptions(): Array<[string, SubscriptionInfo]>;
|
|
996
998
|
/**
|
|
@@ -1002,6 +1004,8 @@ declare class WsStream {
|
|
|
1002
1004
|
onClose(cb: (code: number) => void): void;
|
|
1003
1005
|
onStats(cb: StatsCallback): void;
|
|
1004
1006
|
onDisconnect(cb: DisconnectCallback): void;
|
|
1007
|
+
offStats(cb: StatsCallback): void;
|
|
1008
|
+
offDisconnect(cb: DisconnectCallback): void;
|
|
1005
1009
|
onReconnecting(cb: ReconnectingCallback): void;
|
|
1006
1010
|
onReconnected(cb: ReconnectedCallback): void;
|
|
1007
1011
|
onReconnectionFailed(cb: ReconnectionFailedCallback): void;
|
|
@@ -1078,6 +1082,10 @@ declare class TunnelClient {
|
|
|
1078
1082
|
private origin;
|
|
1079
1083
|
private stream;
|
|
1080
1084
|
health: DaemonHealth;
|
|
1085
|
+
private shimStatsListeners;
|
|
1086
|
+
private shimDisconnectListeners;
|
|
1087
|
+
private shimSubscribedTunnels;
|
|
1088
|
+
private shimListenerSeq;
|
|
1081
1089
|
constructor(options?: TunnelClientOptions);
|
|
1082
1090
|
ensureDaemon(): Promise<void>;
|
|
1083
1091
|
static forRemoteManagement(): Promise<TunnelClient>;
|
|
@@ -1085,11 +1093,7 @@ declare class TunnelClient {
|
|
|
1085
1093
|
* Close the WebSocket connection and cleanup.
|
|
1086
1094
|
*/
|
|
1087
1095
|
close(): void;
|
|
1088
|
-
ping(): Promise<
|
|
1089
|
-
status: string;
|
|
1090
|
-
pid: number;
|
|
1091
|
-
uptime: number;
|
|
1092
|
-
}>;
|
|
1096
|
+
ping(): Promise<PingResponse>;
|
|
1093
1097
|
handleStartV2(config: object, noWait?: boolean, mode?: SessionMode): Promise<TunnelResponseV2 | ErrorResponse>;
|
|
1094
1098
|
handleStart(config: object, noWait?: boolean, mode?: SessionMode): Promise<TunnelResponse | ErrorResponse>;
|
|
1095
1099
|
handleUpdateConfig(config: object, noWait?: boolean): Promise<TunnelResponse | ErrorResponse>;
|
|
@@ -1119,6 +1123,8 @@ declare class TunnelClient {
|
|
|
1119
1123
|
detach(tunnelId: string): void;
|
|
1120
1124
|
onStats(cb: StatsCallback): void;
|
|
1121
1125
|
onDisconnect(cb: DisconnectCallback): void;
|
|
1126
|
+
offStats(cb: StatsCallback): void;
|
|
1127
|
+
offDisconnect(cb: DisconnectCallback): void;
|
|
1122
1128
|
onReconnecting(cb: ReconnectingCallback): void;
|
|
1123
1129
|
onReconnected(cb: ReconnectedCallback): void;
|
|
1124
1130
|
onReconnectionFailed(cb: ReconnectionFailedCallback): void;
|
|
@@ -1131,10 +1137,14 @@ declare class TunnelClient {
|
|
|
1131
1137
|
onDaemonReconnecting(cb: DaemonReconnectingCallback): void;
|
|
1132
1138
|
onDaemonReconnected(cb: DaemonReconnectedCallback): void;
|
|
1133
1139
|
isDaemonLost(): boolean;
|
|
1134
|
-
handleRegisterStatsListener(tunnelId: string, listener:
|
|
1135
|
-
|
|
1136
|
-
|
|
1140
|
+
handleRegisterStatsListener(tunnelId: string, listener: StatsCallback): string;
|
|
1141
|
+
handleUnregisterStatsListener(_tunnelId: string, listenerId: string): void;
|
|
1142
|
+
handleRegisterDisconnectListener(tunnelId: string, listener: DisconnectCallback): string;
|
|
1143
|
+
handleUnregisterDisconnectListener(_tunnelId: string, listenerId: string): void;
|
|
1137
1144
|
handleGetTunnelStats(tunnelId: string): Promise<TunnelUsageType[] | ErrorResponse>;
|
|
1145
|
+
private nextShimListenerId;
|
|
1146
|
+
private subscribeForShim;
|
|
1147
|
+
private releaseShimSubscription;
|
|
1138
1148
|
private assertClient;
|
|
1139
1149
|
}
|
|
1140
1150
|
|
|
@@ -1143,6 +1153,8 @@ interface DaemonInfo {
|
|
|
1143
1153
|
port: number;
|
|
1144
1154
|
startedAt: string;
|
|
1145
1155
|
host?: DaemonHost;
|
|
1156
|
+
/** IPC protocol version the daemon speaks. Absent in daemon.json written by older builds. */
|
|
1157
|
+
ipcVersion?: number;
|
|
1146
1158
|
}
|
|
1147
1159
|
|
|
1148
1160
|
/**
|
package/dist/index.js
CHANGED
|
@@ -14,8 +14,8 @@ import {
|
|
|
14
14
|
initiateRemoteManagement,
|
|
15
15
|
isDaemonRunning,
|
|
16
16
|
startRemoteManagement
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import "./chunk-
|
|
17
|
+
} from "./chunk-B6IKYBLU.js";
|
|
18
|
+
import "./chunk-I4HXBP5G.js";
|
|
19
19
|
import {
|
|
20
20
|
bulkReplace,
|
|
21
21
|
configureStorageLogger,
|
|
@@ -139,7 +139,7 @@ async function verifyAndLoad() {
|
|
|
139
139
|
process.exit(1);
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
|
-
await import("./main-
|
|
142
|
+
await import("./main-VBWX7HTB.js");
|
|
143
143
|
}
|
|
144
144
|
verifyAndLoad().catch((err) => {
|
|
145
145
|
printer_default.fatal(`Failed to start CLI:, ${err}`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runDaemonChild
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-P7XRY5N4.js";
|
|
5
5
|
import {
|
|
6
6
|
RemoteManagementUnauthorizedError,
|
|
7
7
|
TunnelOperations,
|
|
@@ -13,11 +13,11 @@ import {
|
|
|
13
13
|
getRemoteManagementState,
|
|
14
14
|
initiateRemoteManagement,
|
|
15
15
|
isDaemonRunning
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-B6IKYBLU.js";
|
|
17
17
|
import "./chunk-MT44NAXX.js";
|
|
18
18
|
import {
|
|
19
19
|
DaemonHost
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-I4HXBP5G.js";
|
|
21
21
|
import "./chunk-YWXADADB.js";
|
|
22
22
|
import {
|
|
23
23
|
TunnelManager
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pinggy",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.8",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Create secure, shareable tunnels to your localhost and manage them from the command line. ",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
]
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@pinggy/pinggy": "^0.5.
|
|
55
|
+
"@pinggy/pinggy": "^0.5.2",
|
|
56
56
|
"blessed": "^0.1.81",
|
|
57
57
|
"clipboardy": "^5.0.0",
|
|
58
58
|
"mime": "^4.1.0",
|