@decentnetwork/lan 0.1.151 → 0.1.154
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/bin/tun-helper-darwin-amd64 +0 -0
- package/bin/tun-helper-darwin-arm64 +0 -0
- package/bin/tun-helper-linux-amd64 +0 -0
- package/bin/tun-helper-linux-arm64 +0 -0
- package/dist/proxy/multi-exit-router.js +17 -1
- package/dist/types.d.ts +4 -0
- package/dist/ui/desktop/app.js +19 -7
- package/dist/ui/server.js +21 -2
- package/package.json +1 -1
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -114,6 +114,7 @@ const STALL_MIN_OPEN_MS = 4000; // only a tunnel open at least this long can cou
|
|
|
114
114
|
const STALL_MIN_BYTES = 16 * 1024; // delivered at least this = a healthy, real segment
|
|
115
115
|
const STALL_DEAD_BYTES = 1024; // relayed LESS than this = the exit is genuinely dead (only THIS counts as a stall); a small-but-real request (playlist/key/keep-alive, 1-16 KB) is NEUTRAL, so it can't flap a healthy exit out of the pool
|
|
116
116
|
const TRIP_AFTER_STALLS = 3; // consecutive DEAD tunnels before an exit is tripped out (hysteresis)
|
|
117
|
+
const TRIP_AFTER_UPSTREAM_FAILS = 5; // consecutive non-200 CONNECT responses (exit's proxy broken, e.g. 502s everything) before tripping
|
|
117
118
|
const TRIP_COOLDOWN_MS = 30_000; // how long a tripped exit stays excluded before it's retried
|
|
118
119
|
// Measures peak short-window throughput of a byte stream. Feed it every chunk
|
|
119
120
|
// length via add(); read peakBps at the end. Peak (not average) shrugs off TCP
|
|
@@ -196,6 +197,7 @@ export function startMultiExitRouter(opts) {
|
|
|
196
197
|
served: 0,
|
|
197
198
|
lastRtt: null,
|
|
198
199
|
lastSuccessMs: null,
|
|
200
|
+
consecUpstreamFails: 0,
|
|
199
201
|
speedEwma: null,
|
|
200
202
|
bytesDown: 0,
|
|
201
203
|
stalls: 0,
|
|
@@ -458,6 +460,7 @@ export function startMultiExitRouter(opts) {
|
|
|
458
460
|
// tunnels are flowing.
|
|
459
461
|
exit.lastSuccessMs = Date.now();
|
|
460
462
|
exit.fails = 0;
|
|
463
|
+
exit.consecUpstreamFails = 0;
|
|
461
464
|
if (!exit.healthy) {
|
|
462
465
|
exit.healthy = true;
|
|
463
466
|
log(`✓ ${exit.name} [${exit.region}] UP (live traffic)`);
|
|
@@ -527,7 +530,20 @@ export function startMultiExitRouter(opts) {
|
|
|
527
530
|
up.on("error", () => client.destroy());
|
|
528
531
|
}
|
|
529
532
|
else {
|
|
530
|
-
// Exit answered
|
|
533
|
+
// Exit answered with a non-200 CONNECT status. Usually the ORIGIN's
|
|
534
|
+
// fault (a dead host 502s on EVERY exit — tripping would churn the pool),
|
|
535
|
+
// so we just fail over. BUT if THIS exit returns non-200 for many targets
|
|
536
|
+
// in a row (reset on any success), its own proxy is broken — e.g. it 502s
|
|
537
|
+
// everything (dead upstream / wrong version). Trip it so it stops eating
|
|
538
|
+
// the first pick on every request. A single dead origin can't reach the
|
|
539
|
+
// threshold: other hosts still succeed on this exit and reset the streak.
|
|
540
|
+
exit.consecUpstreamFails++;
|
|
541
|
+
if (exit.consecUpstreamFails >= TRIP_AFTER_UPSTREAM_FAILS && exit.trippedUntil <= Date.now()) {
|
|
542
|
+
exit.trippedUntil = Date.now() + TRIP_COOLDOWN_MS;
|
|
543
|
+
exit.healthy = false;
|
|
544
|
+
log(`✗ ${exit.name} [${exit.region}] TRIPPED for ${TRIP_COOLDOWN_MS / 1000}s (proxy returned ${status.trim()} for ${exit.consecUpstreamFails} targets in a row) — excluding from pool`);
|
|
545
|
+
printPool();
|
|
546
|
+
}
|
|
531
547
|
fail(`upstream ${status}`, false);
|
|
532
548
|
}
|
|
533
549
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -110,6 +110,10 @@ export interface BootstrapNode {
|
|
|
110
110
|
port: number;
|
|
111
111
|
pk: string;
|
|
112
112
|
isTcp?: boolean;
|
|
113
|
+
/** Express relays only: set `false` to reach the relay over plain HTTP
|
|
114
|
+
* instead of HTTPS (the payload is already end-to-end encrypted). Lets a
|
|
115
|
+
* relay run on a raw IP with no TLS cert / domain / ICP filing. */
|
|
116
|
+
tls?: boolean;
|
|
113
117
|
}
|
|
114
118
|
export interface CarrierConfig {
|
|
115
119
|
dataDir: string;
|
package/dist/ui/desktop/app.js
CHANGED
|
@@ -173,15 +173,24 @@ async function dkGet(path) {
|
|
|
173
173
|
return r.json();
|
|
174
174
|
}
|
|
175
175
|
async function dkPost(path, body) {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
headers: { "content-type": "application/json" },
|
|
179
|
-
body: JSON.stringify(body || {})
|
|
180
|
-
});
|
|
176
|
+
const ctrl = new AbortController();
|
|
177
|
+
const timer = setTimeout(() => ctrl.abort(), 15e3);
|
|
181
178
|
try {
|
|
182
|
-
|
|
179
|
+
const r = await fetch(path, {
|
|
180
|
+
method: "POST",
|
|
181
|
+
headers: { "content-type": "application/json" },
|
|
182
|
+
body: JSON.stringify(body || {}),
|
|
183
|
+
signal: ctrl.signal
|
|
184
|
+
});
|
|
185
|
+
try {
|
|
186
|
+
return await r.json();
|
|
187
|
+
} catch (e) {
|
|
188
|
+
return { ok: r.ok };
|
|
189
|
+
}
|
|
183
190
|
} catch (e) {
|
|
184
|
-
return { ok:
|
|
191
|
+
return { ok: false, error: e && e.name === "AbortError" ? "timed out" : e && e.message || "network error" };
|
|
192
|
+
} finally {
|
|
193
|
+
clearTimeout(timer);
|
|
185
194
|
}
|
|
186
195
|
}
|
|
187
196
|
function dkCopy(text) {
|
|
@@ -1012,6 +1021,9 @@ function PeerSidebar({ T, peers, requests, activeId, onSelect, onAct, onAdd }) {
|
|
|
1012
1021
|
} else {
|
|
1013
1022
|
setAddState({ kind: "err", msg: r && r.error ? `${T.addFailed}: ${r.error}` : T.addFailed });
|
|
1014
1023
|
}
|
|
1024
|
+
}).catch((e) => {
|
|
1025
|
+
setAddState({ kind: "err", msg: `${T.addFailed}: ${e && e.message || e}` });
|
|
1026
|
+
}).finally(() => {
|
|
1015
1027
|
setTimeout(() => setAddState((s) => s && s.kind !== "sending" ? null : s), 6e3);
|
|
1016
1028
|
});
|
|
1017
1029
|
};
|
package/dist/ui/server.js
CHANGED
|
@@ -665,8 +665,27 @@ export function startFriendUi(opts) {
|
|
|
665
665
|
}
|
|
666
666
|
if (req.method === "POST" && url === "/api/add") {
|
|
667
667
|
const { address } = await readBody(req);
|
|
668
|
-
|
|
669
|
-
|
|
668
|
+
// A friend-request is fire-and-forget: it's delivered when the peer is
|
|
669
|
+
// online and only becomes a friend once they accept. Don't let the UI
|
|
670
|
+
// hang on the network round-trip (DHT lookup / unreachable peer) — kick
|
|
671
|
+
// the send off, but respond within a short window. A fast failure (e.g.
|
|
672
|
+
// a malformed address) still surfaces its real error; a slow send
|
|
673
|
+
// returns optimistic success and continues in the background.
|
|
674
|
+
const sent = opts
|
|
675
|
+
.call({ op: "friend-request", address })
|
|
676
|
+
.then((r) => ({ done: true, r }))
|
|
677
|
+
.catch((e) => ({ done: true, r: { ok: false, error: e instanceof Error ? e.message : String(e) } }));
|
|
678
|
+
const settled = await Promise.race([
|
|
679
|
+
sent,
|
|
680
|
+
new Promise((r) => setTimeout(() => r({ done: false }), 2500)),
|
|
681
|
+
]);
|
|
682
|
+
if (settled.done) {
|
|
683
|
+
sendJson(res, settled.r.ok ? 200 : 400, settled.r);
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
void sent.catch(() => { }); // let it finish in the background, no unhandled rejection
|
|
687
|
+
sendJson(res, 200, { ok: true, queued: true });
|
|
688
|
+
}
|
|
670
689
|
return;
|
|
671
690
|
}
|
|
672
691
|
res.writeHead(404);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.154",
|
|
4
4
|
"description": "Private virtual LAN for self-hosted services and AI agents, built on Elastos Carrier. NAT-traversal, name service, ACL, all over a peer-to-peer mesh — no public IP required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|