pinggy 0.5.7 → 0.5.9
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-2HAVQORM.js} +90 -11
- package/dist/{chunk-GA6UTBOU.js → chunk-4J5YBB4H.js} +9 -4
- package/dist/{daemonChild-STFB5OQL.js → daemonChild-MT6TRMRR.js} +2 -2
- package/dist/index.cjs +98 -12
- 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-KY2O5KNA.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 : "",
|
|
@@ -303,7 +305,10 @@ function pinggyOptionsToTunnelConfigV1(opts, configStoredInCli) {
|
|
|
303
305
|
originalRequestUrl: opts.originalRequestUrl ?? false,
|
|
304
306
|
allowPreflight: opts.allowPreflight ?? false,
|
|
305
307
|
haProxy: opts.haProxy,
|
|
306
|
-
optional:
|
|
308
|
+
optional: {
|
|
309
|
+
...configStoredInCli.optional || {},
|
|
310
|
+
...opts.optional || {}
|
|
311
|
+
}
|
|
307
312
|
};
|
|
308
313
|
}
|
|
309
314
|
function tunnelConfigToPinggyOptions(config) {
|
|
@@ -1677,7 +1682,7 @@ async function ensureDaemonRunning() {
|
|
|
1677
1682
|
return existing;
|
|
1678
1683
|
}
|
|
1679
1684
|
if (process.versions.electron) {
|
|
1680
|
-
const { runDaemonChild } = await import("./daemonChild-
|
|
1685
|
+
const { runDaemonChild } = await import("./daemonChild-MT6TRMRR.js");
|
|
1681
1686
|
const handle = await runDaemonChild({
|
|
1682
1687
|
installSignalHandlers: false,
|
|
1683
1688
|
exitOnFailure: false,
|
|
@@ -1902,6 +1907,9 @@ var WsStream = class {
|
|
|
1902
1907
|
subscriptionCount() {
|
|
1903
1908
|
return this.subscribedTunnels.size;
|
|
1904
1909
|
}
|
|
1910
|
+
isSubscribed(tunnelId) {
|
|
1911
|
+
return this.subscribedTunnels.has(tunnelId);
|
|
1912
|
+
}
|
|
1905
1913
|
/** Snapshot of current subscriptions, for the reconnect path to replay. */
|
|
1906
1914
|
snapshotSubscriptions() {
|
|
1907
1915
|
return Array.from(this.subscribedTunnels.entries());
|
|
@@ -1935,6 +1943,16 @@ var WsStream = class {
|
|
|
1935
1943
|
onDisconnect(cb) {
|
|
1936
1944
|
this.callbacks.disconnect.push(cb);
|
|
1937
1945
|
}
|
|
1946
|
+
// Removal replaces the array instead of splicing it, so a callback that
|
|
1947
|
+
// unregisters itself while handleMessage is iterating cannot make the
|
|
1948
|
+
// dispatch loop skip the next callback. The removal applies from the next
|
|
1949
|
+
// event on.
|
|
1950
|
+
offStats(cb) {
|
|
1951
|
+
this.callbacks.stats = this.callbacks.stats.filter((c) => c !== cb);
|
|
1952
|
+
}
|
|
1953
|
+
offDisconnect(cb) {
|
|
1954
|
+
this.callbacks.disconnect = this.callbacks.disconnect.filter((c) => c !== cb);
|
|
1955
|
+
}
|
|
1938
1956
|
onReconnecting(cb) {
|
|
1939
1957
|
this.callbacks.reconnecting.push(cb);
|
|
1940
1958
|
}
|
|
@@ -2143,6 +2161,12 @@ var DaemonHealth = class {
|
|
|
2143
2161
|
var TunnelClient = class _TunnelClient {
|
|
2144
2162
|
constructor(options = {}) {
|
|
2145
2163
|
this.ipc = null;
|
|
2164
|
+
// App-compat shim state: registered listeners by id, and the tunnels whose
|
|
2165
|
+
// stream subscription those registrations opened (as opposed to attach()).
|
|
2166
|
+
this.shimStatsListeners = /* @__PURE__ */ new Map();
|
|
2167
|
+
this.shimDisconnectListeners = /* @__PURE__ */ new Map();
|
|
2168
|
+
this.shimSubscribedTunnels = /* @__PURE__ */ new Set();
|
|
2169
|
+
this.shimListenerSeq = 0;
|
|
2146
2170
|
this.origin = options.origin ?? "cli";
|
|
2147
2171
|
this.stream = new WsStream(() => {
|
|
2148
2172
|
if (!this.ipc) throw new Error("TunnelClient not initialized. Call ensureDaemon() first.");
|
|
@@ -2290,6 +2314,12 @@ var TunnelClient = class _TunnelClient {
|
|
|
2290
2314
|
onDisconnect(cb) {
|
|
2291
2315
|
this.stream.onDisconnect(cb);
|
|
2292
2316
|
}
|
|
2317
|
+
offStats(cb) {
|
|
2318
|
+
this.stream.offStats(cb);
|
|
2319
|
+
}
|
|
2320
|
+
offDisconnect(cb) {
|
|
2321
|
+
this.stream.offDisconnect(cb);
|
|
2322
|
+
}
|
|
2293
2323
|
onReconnecting(cb) {
|
|
2294
2324
|
this.stream.onReconnecting(cb);
|
|
2295
2325
|
}
|
|
@@ -2327,24 +2357,73 @@ var TunnelClient = class _TunnelClient {
|
|
|
2327
2357
|
isDaemonLost() {
|
|
2328
2358
|
return this.health.isLost();
|
|
2329
2359
|
}
|
|
2330
|
-
// App-compat shims
|
|
2360
|
+
// App-compat shims. Each registration is scoped to ONE tunnel, returns an
|
|
2361
|
+
// id, and is undone by the matching unregister call. Before this, every
|
|
2362
|
+
// register appended a callback that saw every subscribed tunnel's events
|
|
2363
|
+
// and could never be removed, so a consumer that registered per page view
|
|
2364
|
+
// received each stats update N times, forever.
|
|
2365
|
+
//
|
|
2366
|
+
// The first registration for a tunnel opens its stream subscription and
|
|
2367
|
+
// the last unregister drops it with a plain unsubscribe. detach() is not
|
|
2368
|
+
// used here on purpose: it closes the client once nothing is subscribed,
|
|
2369
|
+
// which also stops the daemon-health heartbeat, and a stats consumer must
|
|
2370
|
+
// not have that side effect.
|
|
2331
2371
|
handleRegisterStatsListener(tunnelId, listener) {
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
}
|
|
2372
|
+
const wrapped = (id, stats) => {
|
|
2373
|
+
if (id === tunnelId) listener(id, stats);
|
|
2374
|
+
};
|
|
2375
|
+
const listenerId = this.nextShimListenerId("stats");
|
|
2376
|
+
this.shimStatsListeners.set(listenerId, { tunnelId, wrapped });
|
|
2377
|
+
this.stream.onStats(wrapped);
|
|
2378
|
+
this.subscribeForShim(tunnelId);
|
|
2379
|
+
return listenerId;
|
|
2380
|
+
}
|
|
2381
|
+
handleUnregisterStatsListener(_tunnelId, listenerId) {
|
|
2382
|
+
const entry = this.shimStatsListeners.get(listenerId);
|
|
2383
|
+
if (!entry) return;
|
|
2384
|
+
this.shimStatsListeners.delete(listenerId);
|
|
2385
|
+
this.stream.offStats(entry.wrapped);
|
|
2386
|
+
this.releaseShimSubscription(entry.tunnelId);
|
|
2335
2387
|
}
|
|
2336
2388
|
handleRegisterDisconnectListener(tunnelId, listener) {
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
}
|
|
2340
|
-
|
|
2341
|
-
|
|
2389
|
+
const wrapped = (id, error, messages) => {
|
|
2390
|
+
if (id === tunnelId) listener(id, error, messages);
|
|
2391
|
+
};
|
|
2392
|
+
const listenerId = this.nextShimListenerId("disconnect");
|
|
2393
|
+
this.shimDisconnectListeners.set(listenerId, { tunnelId, wrapped });
|
|
2394
|
+
this.stream.onDisconnect(wrapped);
|
|
2395
|
+
this.subscribeForShim(tunnelId);
|
|
2396
|
+
return listenerId;
|
|
2397
|
+
}
|
|
2398
|
+
handleUnregisterDisconnectListener(_tunnelId, listenerId) {
|
|
2399
|
+
const entry = this.shimDisconnectListeners.get(listenerId);
|
|
2400
|
+
if (!entry) return;
|
|
2401
|
+
this.shimDisconnectListeners.delete(listenerId);
|
|
2402
|
+
this.stream.offDisconnect(entry.wrapped);
|
|
2403
|
+
this.releaseShimSubscription(entry.tunnelId);
|
|
2342
2404
|
}
|
|
2343
2405
|
async handleGetTunnelStats(tunnelId) {
|
|
2344
2406
|
this.assertClient();
|
|
2345
2407
|
return this.ipc.getTunnelStats(tunnelId);
|
|
2346
2408
|
}
|
|
2347
2409
|
// Private
|
|
2410
|
+
nextShimListenerId(kind) {
|
|
2411
|
+
this.shimListenerSeq += 1;
|
|
2412
|
+
return `${kind}-${this.shimListenerSeq}`;
|
|
2413
|
+
}
|
|
2414
|
+
subscribeForShim(tunnelId) {
|
|
2415
|
+
if (!this.stream.isSubscribed(tunnelId)) this.shimSubscribedTunnels.add(tunnelId);
|
|
2416
|
+
this.attach(tunnelId, SessionMode.Detached).catch((err) => {
|
|
2417
|
+
logger.debug("Listener subscribe failed", { tunnelId, error: errorMessage(err) });
|
|
2418
|
+
});
|
|
2419
|
+
}
|
|
2420
|
+
releaseShimSubscription(tunnelId) {
|
|
2421
|
+
if (!this.shimSubscribedTunnels.has(tunnelId)) return;
|
|
2422
|
+
for (const entry of this.shimStatsListeners.values()) if (entry.tunnelId === tunnelId) return;
|
|
2423
|
+
for (const entry of this.shimDisconnectListeners.values()) if (entry.tunnelId === tunnelId) return;
|
|
2424
|
+
this.shimSubscribedTunnels.delete(tunnelId);
|
|
2425
|
+
this.stream.unsubscribe(tunnelId);
|
|
2426
|
+
}
|
|
2348
2427
|
assertClient() {
|
|
2349
2428
|
if (!this.ipc) {
|
|
2350
2429
|
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-2HAVQORM.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-MT6TRMRR.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 : "",
|
|
@@ -2207,7 +2209,10 @@ function pinggyOptionsToTunnelConfigV1(opts, configStoredInCli) {
|
|
|
2207
2209
|
originalRequestUrl: opts.originalRequestUrl ?? false,
|
|
2208
2210
|
allowPreflight: opts.allowPreflight ?? false,
|
|
2209
2211
|
haProxy: opts.haProxy,
|
|
2210
|
-
optional:
|
|
2212
|
+
optional: {
|
|
2213
|
+
...configStoredInCli.optional || {},
|
|
2214
|
+
...opts.optional || {}
|
|
2215
|
+
}
|
|
2211
2216
|
};
|
|
2212
2217
|
}
|
|
2213
2218
|
function tunnelConfigToPinggyOptions(config) {
|
|
@@ -4094,7 +4099,10 @@ var init_defaults = __esm({
|
|
|
4094
4099
|
originalRequestUrl: false,
|
|
4095
4100
|
allowPreflight: false,
|
|
4096
4101
|
reverseProxy: true,
|
|
4097
|
-
autoReconnect: true
|
|
4102
|
+
autoReconnect: true,
|
|
4103
|
+
// 0 = never give up: keep retrying every reconnectInterval until the tunnel
|
|
4104
|
+
// is back. Not user-configurable from the CLI on purpose.
|
|
4105
|
+
maxReconnectAttempts: 0
|
|
4098
4106
|
};
|
|
4099
4107
|
}
|
|
4100
4108
|
});
|
|
@@ -4698,7 +4706,9 @@ function buildFinalConfig(values, positionals, baseConfig) {
|
|
|
4698
4706
|
token: token || (configFromFile?.token || (typeof values.token === "string" ? values.token : "")),
|
|
4699
4707
|
serverAddress: server ? removeIPv6Brackets(server) : configFromFile?.serverAddress || defaultOptions.serverAddress,
|
|
4700
4708
|
isQRCode: qrCode || (configFromFile?.isQRCode || false),
|
|
4701
|
-
|
|
4709
|
+
// `??`, not a truthiness check: a saved `autoReconnect: false` must not
|
|
4710
|
+
// flip back to the default.
|
|
4711
|
+
autoReconnect: configFromFile?.autoReconnect ?? defaultOptions.autoReconnect,
|
|
4702
4712
|
optional: {
|
|
4703
4713
|
serve: configFromFile?.optional?.serve || void 0,
|
|
4704
4714
|
noTui: values.noTui || values.notui || hasRemoteManagement(values) || (configFromFile?.optional?.noTui || false)
|
|
@@ -9801,6 +9811,9 @@ var init_wsStream = __esm({
|
|
|
9801
9811
|
subscriptionCount() {
|
|
9802
9812
|
return this.subscribedTunnels.size;
|
|
9803
9813
|
}
|
|
9814
|
+
isSubscribed(tunnelId) {
|
|
9815
|
+
return this.subscribedTunnels.has(tunnelId);
|
|
9816
|
+
}
|
|
9804
9817
|
/** Snapshot of current subscriptions, for the reconnect path to replay. */
|
|
9805
9818
|
snapshotSubscriptions() {
|
|
9806
9819
|
return Array.from(this.subscribedTunnels.entries());
|
|
@@ -9834,6 +9847,16 @@ var init_wsStream = __esm({
|
|
|
9834
9847
|
onDisconnect(cb) {
|
|
9835
9848
|
this.callbacks.disconnect.push(cb);
|
|
9836
9849
|
}
|
|
9850
|
+
// Removal replaces the array instead of splicing it, so a callback that
|
|
9851
|
+
// unregisters itself while handleMessage is iterating cannot make the
|
|
9852
|
+
// dispatch loop skip the next callback. The removal applies from the next
|
|
9853
|
+
// event on.
|
|
9854
|
+
offStats(cb) {
|
|
9855
|
+
this.callbacks.stats = this.callbacks.stats.filter((c) => c !== cb);
|
|
9856
|
+
}
|
|
9857
|
+
offDisconnect(cb) {
|
|
9858
|
+
this.callbacks.disconnect = this.callbacks.disconnect.filter((c) => c !== cb);
|
|
9859
|
+
}
|
|
9837
9860
|
onReconnecting(cb) {
|
|
9838
9861
|
this.callbacks.reconnecting.push(cb);
|
|
9839
9862
|
}
|
|
@@ -10068,6 +10091,8 @@ var init_tunnelClient = __esm({
|
|
|
10068
10091
|
init_cjs_shims();
|
|
10069
10092
|
init_ipcClient();
|
|
10070
10093
|
init_daemonManager();
|
|
10094
|
+
init_logger();
|
|
10095
|
+
init_util();
|
|
10071
10096
|
init_wsStream();
|
|
10072
10097
|
init_daemonHealth();
|
|
10073
10098
|
init_daemonTunnelHandler();
|
|
@@ -10075,6 +10100,12 @@ var init_tunnelClient = __esm({
|
|
|
10075
10100
|
TunnelClient = class _TunnelClient {
|
|
10076
10101
|
constructor(options = {}) {
|
|
10077
10102
|
this.ipc = null;
|
|
10103
|
+
// App-compat shim state: registered listeners by id, and the tunnels whose
|
|
10104
|
+
// stream subscription those registrations opened (as opposed to attach()).
|
|
10105
|
+
this.shimStatsListeners = /* @__PURE__ */ new Map();
|
|
10106
|
+
this.shimDisconnectListeners = /* @__PURE__ */ new Map();
|
|
10107
|
+
this.shimSubscribedTunnels = /* @__PURE__ */ new Set();
|
|
10108
|
+
this.shimListenerSeq = 0;
|
|
10078
10109
|
this.origin = options.origin ?? "cli";
|
|
10079
10110
|
this.stream = new WsStream(() => {
|
|
10080
10111
|
if (!this.ipc) throw new Error("TunnelClient not initialized. Call ensureDaemon() first.");
|
|
@@ -10222,6 +10253,12 @@ var init_tunnelClient = __esm({
|
|
|
10222
10253
|
onDisconnect(cb) {
|
|
10223
10254
|
this.stream.onDisconnect(cb);
|
|
10224
10255
|
}
|
|
10256
|
+
offStats(cb) {
|
|
10257
|
+
this.stream.offStats(cb);
|
|
10258
|
+
}
|
|
10259
|
+
offDisconnect(cb) {
|
|
10260
|
+
this.stream.offDisconnect(cb);
|
|
10261
|
+
}
|
|
10225
10262
|
onReconnecting(cb) {
|
|
10226
10263
|
this.stream.onReconnecting(cb);
|
|
10227
10264
|
}
|
|
@@ -10259,24 +10296,73 @@ var init_tunnelClient = __esm({
|
|
|
10259
10296
|
isDaemonLost() {
|
|
10260
10297
|
return this.health.isLost();
|
|
10261
10298
|
}
|
|
10262
|
-
// App-compat shims
|
|
10299
|
+
// App-compat shims. Each registration is scoped to ONE tunnel, returns an
|
|
10300
|
+
// id, and is undone by the matching unregister call. Before this, every
|
|
10301
|
+
// register appended a callback that saw every subscribed tunnel's events
|
|
10302
|
+
// and could never be removed, so a consumer that registered per page view
|
|
10303
|
+
// received each stats update N times, forever.
|
|
10304
|
+
//
|
|
10305
|
+
// The first registration for a tunnel opens its stream subscription and
|
|
10306
|
+
// the last unregister drops it with a plain unsubscribe. detach() is not
|
|
10307
|
+
// used here on purpose: it closes the client once nothing is subscribed,
|
|
10308
|
+
// which also stops the daemon-health heartbeat, and a stats consumer must
|
|
10309
|
+
// not have that side effect.
|
|
10263
10310
|
handleRegisterStatsListener(tunnelId, listener) {
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
}
|
|
10311
|
+
const wrapped = (id, stats) => {
|
|
10312
|
+
if (id === tunnelId) listener(id, stats);
|
|
10313
|
+
};
|
|
10314
|
+
const listenerId = this.nextShimListenerId("stats");
|
|
10315
|
+
this.shimStatsListeners.set(listenerId, { tunnelId, wrapped });
|
|
10316
|
+
this.stream.onStats(wrapped);
|
|
10317
|
+
this.subscribeForShim(tunnelId);
|
|
10318
|
+
return listenerId;
|
|
10319
|
+
}
|
|
10320
|
+
handleUnregisterStatsListener(_tunnelId, listenerId) {
|
|
10321
|
+
const entry = this.shimStatsListeners.get(listenerId);
|
|
10322
|
+
if (!entry) return;
|
|
10323
|
+
this.shimStatsListeners.delete(listenerId);
|
|
10324
|
+
this.stream.offStats(entry.wrapped);
|
|
10325
|
+
this.releaseShimSubscription(entry.tunnelId);
|
|
10267
10326
|
}
|
|
10268
10327
|
handleRegisterDisconnectListener(tunnelId, listener) {
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
}
|
|
10272
|
-
|
|
10273
|
-
|
|
10328
|
+
const wrapped = (id, error, messages) => {
|
|
10329
|
+
if (id === tunnelId) listener(id, error, messages);
|
|
10330
|
+
};
|
|
10331
|
+
const listenerId = this.nextShimListenerId("disconnect");
|
|
10332
|
+
this.shimDisconnectListeners.set(listenerId, { tunnelId, wrapped });
|
|
10333
|
+
this.stream.onDisconnect(wrapped);
|
|
10334
|
+
this.subscribeForShim(tunnelId);
|
|
10335
|
+
return listenerId;
|
|
10336
|
+
}
|
|
10337
|
+
handleUnregisterDisconnectListener(_tunnelId, listenerId) {
|
|
10338
|
+
const entry = this.shimDisconnectListeners.get(listenerId);
|
|
10339
|
+
if (!entry) return;
|
|
10340
|
+
this.shimDisconnectListeners.delete(listenerId);
|
|
10341
|
+
this.stream.offDisconnect(entry.wrapped);
|
|
10342
|
+
this.releaseShimSubscription(entry.tunnelId);
|
|
10274
10343
|
}
|
|
10275
10344
|
async handleGetTunnelStats(tunnelId) {
|
|
10276
10345
|
this.assertClient();
|
|
10277
10346
|
return this.ipc.getTunnelStats(tunnelId);
|
|
10278
10347
|
}
|
|
10279
10348
|
// Private
|
|
10349
|
+
nextShimListenerId(kind) {
|
|
10350
|
+
this.shimListenerSeq += 1;
|
|
10351
|
+
return `${kind}-${this.shimListenerSeq}`;
|
|
10352
|
+
}
|
|
10353
|
+
subscribeForShim(tunnelId) {
|
|
10354
|
+
if (!this.stream.isSubscribed(tunnelId)) this.shimSubscribedTunnels.add(tunnelId);
|
|
10355
|
+
this.attach(tunnelId, SessionMode.Detached).catch((err) => {
|
|
10356
|
+
logger.debug("Listener subscribe failed", { tunnelId, error: errorMessage(err) });
|
|
10357
|
+
});
|
|
10358
|
+
}
|
|
10359
|
+
releaseShimSubscription(tunnelId) {
|
|
10360
|
+
if (!this.shimSubscribedTunnels.has(tunnelId)) return;
|
|
10361
|
+
for (const entry of this.shimStatsListeners.values()) if (entry.tunnelId === tunnelId) return;
|
|
10362
|
+
for (const entry of this.shimDisconnectListeners.values()) if (entry.tunnelId === tunnelId) return;
|
|
10363
|
+
this.shimSubscribedTunnels.delete(tunnelId);
|
|
10364
|
+
this.stream.unsubscribe(tunnelId);
|
|
10365
|
+
}
|
|
10280
10366
|
assertClient() {
|
|
10281
10367
|
if (!this.ipc) {
|
|
10282
10368
|
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-2HAVQORM.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-KY2O5KNA.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-4J5YBB4H.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-2HAVQORM.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.9",
|
|
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",
|