pinggy 0.5.7 → 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 +14 -1
- package/README.md +1 -1
- package/dist/{chunk-HS3OI4OL.js → chunk-B6IKYBLU.js} +86 -10
- package/dist/{chunk-GA6UTBOU.js → chunk-P7XRY5N4.js} +9 -4
- package/dist/{daemonChild-STFB5OQL.js → daemonChild-ZPZK5F4Q.js} +2 -2
- package/dist/index.cjs +94 -11
- package/dist/index.d.cts +16 -3
- package/dist/index.d.ts +16 -3
- package/dist/index.js +2 -2
- package/dist/{main-M5PCTJTB.js → main-VBWX7HTB.js} +2 -2
- package/package.json +2 -2
package/CLAUDE.md
CHANGED
|
@@ -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
|
|
|
@@ -290,6 +290,8 @@ function pinggyOptionsToTunnelConfigV1(opts, configStoredInCli) {
|
|
|
290
290
|
serverAddress: opts.serverAddress || "a.pinggy.io:443",
|
|
291
291
|
token: opts.token || "",
|
|
292
292
|
autoReconnect: opts.autoReconnect ?? true,
|
|
293
|
+
reconnectInterval: opts.reconnectInterval,
|
|
294
|
+
maxReconnectAttempts: opts.maxReconnectAttempts,
|
|
293
295
|
force: opts.force ?? false,
|
|
294
296
|
webDebugger: opts.webDebugger || "",
|
|
295
297
|
forwarding: opts.forwarding ? opts.forwarding : "",
|
|
@@ -1677,7 +1679,7 @@ async function ensureDaemonRunning() {
|
|
|
1677
1679
|
return existing;
|
|
1678
1680
|
}
|
|
1679
1681
|
if (process.versions.electron) {
|
|
1680
|
-
const { runDaemonChild } = await import("./daemonChild-
|
|
1682
|
+
const { runDaemonChild } = await import("./daemonChild-ZPZK5F4Q.js");
|
|
1681
1683
|
const handle = await runDaemonChild({
|
|
1682
1684
|
installSignalHandlers: false,
|
|
1683
1685
|
exitOnFailure: false,
|
|
@@ -1902,6 +1904,9 @@ var WsStream = class {
|
|
|
1902
1904
|
subscriptionCount() {
|
|
1903
1905
|
return this.subscribedTunnels.size;
|
|
1904
1906
|
}
|
|
1907
|
+
isSubscribed(tunnelId) {
|
|
1908
|
+
return this.subscribedTunnels.has(tunnelId);
|
|
1909
|
+
}
|
|
1905
1910
|
/** Snapshot of current subscriptions, for the reconnect path to replay. */
|
|
1906
1911
|
snapshotSubscriptions() {
|
|
1907
1912
|
return Array.from(this.subscribedTunnels.entries());
|
|
@@ -1935,6 +1940,16 @@ var WsStream = class {
|
|
|
1935
1940
|
onDisconnect(cb) {
|
|
1936
1941
|
this.callbacks.disconnect.push(cb);
|
|
1937
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
|
+
}
|
|
1938
1953
|
onReconnecting(cb) {
|
|
1939
1954
|
this.callbacks.reconnecting.push(cb);
|
|
1940
1955
|
}
|
|
@@ -2143,6 +2158,12 @@ var DaemonHealth = class {
|
|
|
2143
2158
|
var TunnelClient = class _TunnelClient {
|
|
2144
2159
|
constructor(options = {}) {
|
|
2145
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;
|
|
2146
2167
|
this.origin = options.origin ?? "cli";
|
|
2147
2168
|
this.stream = new WsStream(() => {
|
|
2148
2169
|
if (!this.ipc) throw new Error("TunnelClient not initialized. Call ensureDaemon() first.");
|
|
@@ -2290,6 +2311,12 @@ var TunnelClient = class _TunnelClient {
|
|
|
2290
2311
|
onDisconnect(cb) {
|
|
2291
2312
|
this.stream.onDisconnect(cb);
|
|
2292
2313
|
}
|
|
2314
|
+
offStats(cb) {
|
|
2315
|
+
this.stream.offStats(cb);
|
|
2316
|
+
}
|
|
2317
|
+
offDisconnect(cb) {
|
|
2318
|
+
this.stream.offDisconnect(cb);
|
|
2319
|
+
}
|
|
2293
2320
|
onReconnecting(cb) {
|
|
2294
2321
|
this.stream.onReconnecting(cb);
|
|
2295
2322
|
}
|
|
@@ -2327,24 +2354,73 @@ var TunnelClient = class _TunnelClient {
|
|
|
2327
2354
|
isDaemonLost() {
|
|
2328
2355
|
return this.health.isLost();
|
|
2329
2356
|
}
|
|
2330
|
-
// 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.
|
|
2331
2368
|
handleRegisterStatsListener(tunnelId, listener) {
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
}
|
|
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);
|
|
2335
2384
|
}
|
|
2336
2385
|
handleRegisterDisconnectListener(tunnelId, listener) {
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
}
|
|
2340
|
-
|
|
2341
|
-
|
|
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);
|
|
2342
2401
|
}
|
|
2343
2402
|
async handleGetTunnelStats(tunnelId) {
|
|
2344
2403
|
this.assertClient();
|
|
2345
2404
|
return this.ipc.getTunnelStats(tunnelId);
|
|
2346
2405
|
}
|
|
2347
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
|
+
}
|
|
2348
2424
|
assertClient() {
|
|
2349
2425
|
if (!this.ipc) {
|
|
2350
2426
|
throw new Error("TunnelClient not initialized. Call ensureDaemon() first.");
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
startDaemon,
|
|
14
14
|
startRemoteManagement,
|
|
15
15
|
stopDaemon
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-B6IKYBLU.js";
|
|
17
17
|
import {
|
|
18
18
|
readDaemonConfig
|
|
19
19
|
} from "./chunk-MT44NAXX.js";
|
|
@@ -275,7 +275,10 @@ var defaultOptions = {
|
|
|
275
275
|
originalRequestUrl: false,
|
|
276
276
|
allowPreflight: false,
|
|
277
277
|
reverseProxy: true,
|
|
278
|
-
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
|
|
279
282
|
};
|
|
280
283
|
|
|
281
284
|
// src/cli/extendedOptions.ts
|
|
@@ -883,7 +886,9 @@ function buildFinalConfig(values, positionals, baseConfig) {
|
|
|
883
886
|
token: token || (configFromFile?.token || (typeof values.token === "string" ? values.token : "")),
|
|
884
887
|
serverAddress: server ? removeIPv6Brackets(server) : configFromFile?.serverAddress || defaultOptions.serverAddress,
|
|
885
888
|
isQRCode: qrCode || (configFromFile?.isQRCode || false),
|
|
886
|
-
|
|
889
|
+
// `??`, not a truthiness check: a saved `autoReconnect: false` must not
|
|
890
|
+
// flip back to the default.
|
|
891
|
+
autoReconnect: configFromFile?.autoReconnect ?? defaultOptions.autoReconnect,
|
|
887
892
|
optional: {
|
|
888
893
|
serve: configFromFile?.optional?.serve || void 0,
|
|
889
894
|
noTui: values.noTui || values.notui || hasRemoteManagement(values) || (configFromFile?.optional?.noTui || false)
|
|
@@ -3458,7 +3463,7 @@ async function main() {
|
|
|
3458
3463
|
const { values, positionals, hasAnyArgs } = parseCliArgs(cliOptions);
|
|
3459
3464
|
configureLogger(values);
|
|
3460
3465
|
if (values["_daemon-child"]) {
|
|
3461
|
-
const { runDaemonChild: runDaemonChild2 } = await import("./daemonChild-
|
|
3466
|
+
const { runDaemonChild: runDaemonChild2 } = await import("./daemonChild-ZPZK5F4Q.js");
|
|
3462
3467
|
await runDaemonChild2();
|
|
3463
3468
|
return;
|
|
3464
3469
|
}
|
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 : "",
|
|
@@ -4094,7 +4096,10 @@ var init_defaults = __esm({
|
|
|
4094
4096
|
originalRequestUrl: false,
|
|
4095
4097
|
allowPreflight: false,
|
|
4096
4098
|
reverseProxy: true,
|
|
4097
|
-
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
|
|
4098
4103
|
};
|
|
4099
4104
|
}
|
|
4100
4105
|
});
|
|
@@ -4698,7 +4703,9 @@ function buildFinalConfig(values, positionals, baseConfig) {
|
|
|
4698
4703
|
token: token || (configFromFile?.token || (typeof values.token === "string" ? values.token : "")),
|
|
4699
4704
|
serverAddress: server ? removeIPv6Brackets(server) : configFromFile?.serverAddress || defaultOptions.serverAddress,
|
|
4700
4705
|
isQRCode: qrCode || (configFromFile?.isQRCode || false),
|
|
4701
|
-
|
|
4706
|
+
// `??`, not a truthiness check: a saved `autoReconnect: false` must not
|
|
4707
|
+
// flip back to the default.
|
|
4708
|
+
autoReconnect: configFromFile?.autoReconnect ?? defaultOptions.autoReconnect,
|
|
4702
4709
|
optional: {
|
|
4703
4710
|
serve: configFromFile?.optional?.serve || void 0,
|
|
4704
4711
|
noTui: values.noTui || values.notui || hasRemoteManagement(values) || (configFromFile?.optional?.noTui || false)
|
|
@@ -9801,6 +9808,9 @@ var init_wsStream = __esm({
|
|
|
9801
9808
|
subscriptionCount() {
|
|
9802
9809
|
return this.subscribedTunnels.size;
|
|
9803
9810
|
}
|
|
9811
|
+
isSubscribed(tunnelId) {
|
|
9812
|
+
return this.subscribedTunnels.has(tunnelId);
|
|
9813
|
+
}
|
|
9804
9814
|
/** Snapshot of current subscriptions, for the reconnect path to replay. */
|
|
9805
9815
|
snapshotSubscriptions() {
|
|
9806
9816
|
return Array.from(this.subscribedTunnels.entries());
|
|
@@ -9834,6 +9844,16 @@ var init_wsStream = __esm({
|
|
|
9834
9844
|
onDisconnect(cb) {
|
|
9835
9845
|
this.callbacks.disconnect.push(cb);
|
|
9836
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
|
+
}
|
|
9837
9857
|
onReconnecting(cb) {
|
|
9838
9858
|
this.callbacks.reconnecting.push(cb);
|
|
9839
9859
|
}
|
|
@@ -10068,6 +10088,8 @@ var init_tunnelClient = __esm({
|
|
|
10068
10088
|
init_cjs_shims();
|
|
10069
10089
|
init_ipcClient();
|
|
10070
10090
|
init_daemonManager();
|
|
10091
|
+
init_logger();
|
|
10092
|
+
init_util();
|
|
10071
10093
|
init_wsStream();
|
|
10072
10094
|
init_daemonHealth();
|
|
10073
10095
|
init_daemonTunnelHandler();
|
|
@@ -10075,6 +10097,12 @@ var init_tunnelClient = __esm({
|
|
|
10075
10097
|
TunnelClient = class _TunnelClient {
|
|
10076
10098
|
constructor(options = {}) {
|
|
10077
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;
|
|
10078
10106
|
this.origin = options.origin ?? "cli";
|
|
10079
10107
|
this.stream = new WsStream(() => {
|
|
10080
10108
|
if (!this.ipc) throw new Error("TunnelClient not initialized. Call ensureDaemon() first.");
|
|
@@ -10222,6 +10250,12 @@ var init_tunnelClient = __esm({
|
|
|
10222
10250
|
onDisconnect(cb) {
|
|
10223
10251
|
this.stream.onDisconnect(cb);
|
|
10224
10252
|
}
|
|
10253
|
+
offStats(cb) {
|
|
10254
|
+
this.stream.offStats(cb);
|
|
10255
|
+
}
|
|
10256
|
+
offDisconnect(cb) {
|
|
10257
|
+
this.stream.offDisconnect(cb);
|
|
10258
|
+
}
|
|
10225
10259
|
onReconnecting(cb) {
|
|
10226
10260
|
this.stream.onReconnecting(cb);
|
|
10227
10261
|
}
|
|
@@ -10259,24 +10293,73 @@ var init_tunnelClient = __esm({
|
|
|
10259
10293
|
isDaemonLost() {
|
|
10260
10294
|
return this.health.isLost();
|
|
10261
10295
|
}
|
|
10262
|
-
// 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.
|
|
10263
10307
|
handleRegisterStatsListener(tunnelId, listener) {
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
}
|
|
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);
|
|
10267
10323
|
}
|
|
10268
10324
|
handleRegisterDisconnectListener(tunnelId, listener) {
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
}
|
|
10272
|
-
|
|
10273
|
-
|
|
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);
|
|
10274
10340
|
}
|
|
10275
10341
|
async handleGetTunnelStats(tunnelId) {
|
|
10276
10342
|
this.assertClient();
|
|
10277
10343
|
return this.ipc.getTunnelStats(tunnelId);
|
|
10278
10344
|
}
|
|
10279
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
|
+
}
|
|
10280
10363
|
assertClient() {
|
|
10281
10364
|
if (!this.ipc) {
|
|
10282
10365
|
throw new Error("TunnelClient not initialized. Call ensureDaemon() first.");
|
package/dist/index.d.cts
CHANGED
|
@@ -992,6 +992,7 @@ declare class WsStream {
|
|
|
992
992
|
unsubscribe(tunnelId: string): boolean;
|
|
993
993
|
hasSubscriptions(): boolean;
|
|
994
994
|
subscriptionCount(): number;
|
|
995
|
+
isSubscribed(tunnelId: string): boolean;
|
|
995
996
|
/** Snapshot of current subscriptions, for the reconnect path to replay. */
|
|
996
997
|
snapshotSubscriptions(): Array<[string, SubscriptionInfo]>;
|
|
997
998
|
/**
|
|
@@ -1003,6 +1004,8 @@ declare class WsStream {
|
|
|
1003
1004
|
onClose(cb: (code: number) => void): void;
|
|
1004
1005
|
onStats(cb: StatsCallback): void;
|
|
1005
1006
|
onDisconnect(cb: DisconnectCallback): void;
|
|
1007
|
+
offStats(cb: StatsCallback): void;
|
|
1008
|
+
offDisconnect(cb: DisconnectCallback): void;
|
|
1006
1009
|
onReconnecting(cb: ReconnectingCallback): void;
|
|
1007
1010
|
onReconnected(cb: ReconnectedCallback): void;
|
|
1008
1011
|
onReconnectionFailed(cb: ReconnectionFailedCallback): void;
|
|
@@ -1079,6 +1082,10 @@ declare class TunnelClient {
|
|
|
1079
1082
|
private origin;
|
|
1080
1083
|
private stream;
|
|
1081
1084
|
health: DaemonHealth;
|
|
1085
|
+
private shimStatsListeners;
|
|
1086
|
+
private shimDisconnectListeners;
|
|
1087
|
+
private shimSubscribedTunnels;
|
|
1088
|
+
private shimListenerSeq;
|
|
1082
1089
|
constructor(options?: TunnelClientOptions);
|
|
1083
1090
|
ensureDaemon(): Promise<void>;
|
|
1084
1091
|
static forRemoteManagement(): Promise<TunnelClient>;
|
|
@@ -1116,6 +1123,8 @@ declare class TunnelClient {
|
|
|
1116
1123
|
detach(tunnelId: string): void;
|
|
1117
1124
|
onStats(cb: StatsCallback): void;
|
|
1118
1125
|
onDisconnect(cb: DisconnectCallback): void;
|
|
1126
|
+
offStats(cb: StatsCallback): void;
|
|
1127
|
+
offDisconnect(cb: DisconnectCallback): void;
|
|
1119
1128
|
onReconnecting(cb: ReconnectingCallback): void;
|
|
1120
1129
|
onReconnected(cb: ReconnectedCallback): void;
|
|
1121
1130
|
onReconnectionFailed(cb: ReconnectionFailedCallback): void;
|
|
@@ -1128,10 +1137,14 @@ declare class TunnelClient {
|
|
|
1128
1137
|
onDaemonReconnecting(cb: DaemonReconnectingCallback): void;
|
|
1129
1138
|
onDaemonReconnected(cb: DaemonReconnectedCallback): void;
|
|
1130
1139
|
isDaemonLost(): boolean;
|
|
1131
|
-
handleRegisterStatsListener(tunnelId: string, listener:
|
|
1132
|
-
|
|
1133
|
-
|
|
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;
|
|
1134
1144
|
handleGetTunnelStats(tunnelId: string): Promise<TunnelUsageType[] | ErrorResponse>;
|
|
1145
|
+
private nextShimListenerId;
|
|
1146
|
+
private subscribeForShim;
|
|
1147
|
+
private releaseShimSubscription;
|
|
1135
1148
|
private assertClient;
|
|
1136
1149
|
}
|
|
1137
1150
|
|
package/dist/index.d.ts
CHANGED
|
@@ -992,6 +992,7 @@ declare class WsStream {
|
|
|
992
992
|
unsubscribe(tunnelId: string): boolean;
|
|
993
993
|
hasSubscriptions(): boolean;
|
|
994
994
|
subscriptionCount(): number;
|
|
995
|
+
isSubscribed(tunnelId: string): boolean;
|
|
995
996
|
/** Snapshot of current subscriptions, for the reconnect path to replay. */
|
|
996
997
|
snapshotSubscriptions(): Array<[string, SubscriptionInfo]>;
|
|
997
998
|
/**
|
|
@@ -1003,6 +1004,8 @@ declare class WsStream {
|
|
|
1003
1004
|
onClose(cb: (code: number) => void): void;
|
|
1004
1005
|
onStats(cb: StatsCallback): void;
|
|
1005
1006
|
onDisconnect(cb: DisconnectCallback): void;
|
|
1007
|
+
offStats(cb: StatsCallback): void;
|
|
1008
|
+
offDisconnect(cb: DisconnectCallback): void;
|
|
1006
1009
|
onReconnecting(cb: ReconnectingCallback): void;
|
|
1007
1010
|
onReconnected(cb: ReconnectedCallback): void;
|
|
1008
1011
|
onReconnectionFailed(cb: ReconnectionFailedCallback): void;
|
|
@@ -1079,6 +1082,10 @@ declare class TunnelClient {
|
|
|
1079
1082
|
private origin;
|
|
1080
1083
|
private stream;
|
|
1081
1084
|
health: DaemonHealth;
|
|
1085
|
+
private shimStatsListeners;
|
|
1086
|
+
private shimDisconnectListeners;
|
|
1087
|
+
private shimSubscribedTunnels;
|
|
1088
|
+
private shimListenerSeq;
|
|
1082
1089
|
constructor(options?: TunnelClientOptions);
|
|
1083
1090
|
ensureDaemon(): Promise<void>;
|
|
1084
1091
|
static forRemoteManagement(): Promise<TunnelClient>;
|
|
@@ -1116,6 +1123,8 @@ declare class TunnelClient {
|
|
|
1116
1123
|
detach(tunnelId: string): void;
|
|
1117
1124
|
onStats(cb: StatsCallback): void;
|
|
1118
1125
|
onDisconnect(cb: DisconnectCallback): void;
|
|
1126
|
+
offStats(cb: StatsCallback): void;
|
|
1127
|
+
offDisconnect(cb: DisconnectCallback): void;
|
|
1119
1128
|
onReconnecting(cb: ReconnectingCallback): void;
|
|
1120
1129
|
onReconnected(cb: ReconnectedCallback): void;
|
|
1121
1130
|
onReconnectionFailed(cb: ReconnectionFailedCallback): void;
|
|
@@ -1128,10 +1137,14 @@ declare class TunnelClient {
|
|
|
1128
1137
|
onDaemonReconnecting(cb: DaemonReconnectingCallback): void;
|
|
1129
1138
|
onDaemonReconnected(cb: DaemonReconnectedCallback): void;
|
|
1130
1139
|
isDaemonLost(): boolean;
|
|
1131
|
-
handleRegisterStatsListener(tunnelId: string, listener:
|
|
1132
|
-
|
|
1133
|
-
|
|
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;
|
|
1134
1144
|
handleGetTunnelStats(tunnelId: string): Promise<TunnelUsageType[] | ErrorResponse>;
|
|
1145
|
+
private nextShimListenerId;
|
|
1146
|
+
private subscribeForShim;
|
|
1147
|
+
private releaseShimSubscription;
|
|
1135
1148
|
private assertClient;
|
|
1136
1149
|
}
|
|
1137
1150
|
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
initiateRemoteManagement,
|
|
15
15
|
isDaemonRunning,
|
|
16
16
|
startRemoteManagement
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-B6IKYBLU.js";
|
|
18
18
|
import "./chunk-I4HXBP5G.js";
|
|
19
19
|
import {
|
|
20
20
|
bulkReplace,
|
|
@@ -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,7 +13,7 @@ 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
|
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",
|