@decentnetwork/lan 0.1.178 → 0.1.180

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.
Binary file
Binary file
Binary file
Binary file
@@ -131,6 +131,11 @@ const STALL_MIN_BYTES = 16 * 1024; // delivered at least this = a healthy, real
131
131
  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
132
132
  const TRIP_AFTER_STALLS = 3; // consecutive DEAD tunnels before an exit is tripped out (hysteresis)
133
133
  const TRIP_AFTER_UPSTREAM_FAILS = 5; // consecutive non-200 CONNECT responses (exit's proxy broken, e.g. 502s everything) before tripping
134
+ // Keep a proven exit in rotation only if its measured speed is at least this
135
+ // fraction of the region's FASTEST proven exit. Below it (e.g. a 0.4 Mbps node
136
+ // next to a 1.2 Mbps one) it's benched while a healthy exit exists, so heavy
137
+ // streams don't stall on it. Unproven (unmeasured) exits are never benched.
138
+ const SLOW_EXIT_FLOOR_FRACTION = 0.4;
134
139
  const TRIP_COOLDOWN_MS = 30_000; // how long a tripped exit stays excluded before it's retried
135
140
  // Measures peak short-window throughput of a byte stream. Feed it every chunk
136
141
  // length via add(); read peakBps at the end. Peak (not average) shrugs off TCP
@@ -430,6 +435,20 @@ export function startMultiExitRouter(opts) {
430
435
  // Unproven exits still get overflow traffic once the proven ones fill up,
431
436
  // which measures them safely without risking the video.
432
437
  const unprovenBps = measuredVals.length > 0 ? measuredVals[0] : 800_000;
438
+ // Speed FLOOR: exclude exits that are dramatically slower than the best
439
+ // PROVEN exit. `score = speed/(active+1)` self-balances, but once the fast
440
+ // exit fills, a bandwidth-heavy stream (CCTV opens dozens of parallel
441
+ // segment tunnels) spills onto a degraded node (observed 0.4 Mbps vs 1.2
442
+ // Mbps) and stalls/rebuffers. Keep such nodes OUT of rotation while a
443
+ // healthy-speed exit exists; unproven exits (null speed) stay in so they
444
+ // still get measured, and we never empty the pool.
445
+ if (measuredVals.length >= 2) {
446
+ const best = measuredVals[measuredVals.length - 1];
447
+ const floor = best * SLOW_EXIT_FLOOR_FRACTION;
448
+ const fast = pool.filter((e) => e.speedEwma === null || e.speedEwma >= floor);
449
+ if (fast.length > 0)
450
+ pool = fast;
451
+ }
433
452
  const score = (e) => (e.speedEwma ?? unprovenBps) / (e.active + 1);
434
453
  return [...pool].sort((a, b) => score(b) - score(a) || a.served - b.served);
435
454
  }
@@ -291,6 +291,7 @@ var PeerWebRTC = (() => {
291
291
  * so the peer receives it. Works even with no camera. Ending the OS "stop
292
292
  * sharing" prompt reverts automatically. */
293
293
  async shareScreen(callId) {
294
+ var _a, _b, _c;
294
295
  const session = __privateGet(this, _sessions).get(__privateMethod(this, _CallEngine_instances, normId_fn).call(this, callId));
295
296
  if (!session)
296
297
  throw new Error(`shareScreen: no call ${callId}`);
@@ -300,8 +301,11 @@ var PeerWebRTC = (() => {
300
301
  const track = screen.getVideoTracks()[0];
301
302
  if (!track)
302
303
  return;
303
- if (!session.screenStream)
304
- session.cameraStream = session.localStream;
304
+ if (!session.screenStream) {
305
+ session.hadCamera = !!((_a = session.localStream) == null ? void 0 : _a.getVideoTracks().length);
306
+ for (const t of (_c = (_b = session.localStream) == null ? void 0 : _b.getVideoTracks()) != null ? _c : [])
307
+ t.stop();
308
+ }
305
309
  session.screenStream = screen;
306
310
  await __privateMethod(this, _CallEngine_instances, setOutgoingVideoTrack_fn).call(this, session, track, screen);
307
311
  track.addEventListener("ended", () => {
@@ -310,7 +314,7 @@ var PeerWebRTC = (() => {
310
314
  await __privateMethod(this, _CallEngine_instances, renegotiate_fn).call(this, session);
311
315
  __privateMethod(this, _CallEngine_instances, log_fn).call(this, `shareScreen started for ${callId}`);
312
316
  }
313
- /** Stop screen sharing; restore the camera track if there was one. */
317
+ /** Stop screen sharing; re-acquire the camera if there was one. */
314
318
  async stopScreenShare(callId) {
315
319
  var _a;
316
320
  const session = __privateGet(this, _sessions).get(__privateMethod(this, _CallEngine_instances, normId_fn).call(this, callId));
@@ -319,9 +323,12 @@ var PeerWebRTC = (() => {
319
323
  for (const t of session.screenStream.getTracks())
320
324
  t.stop();
321
325
  session.screenStream = void 0;
322
- const camTrack = (_a = session.cameraStream) == null ? void 0 : _a.getVideoTracks()[0];
323
- await __privateMethod(this, _CallEngine_instances, setOutgoingVideoTrack_fn).call(this, session, camTrack != null ? camTrack : null, session.cameraStream);
324
- session.cameraStream = void 0;
326
+ let cam;
327
+ if (session.hadCamera && __privateGet(this, _opts).getLocalMedia) {
328
+ cam = await __privateGet(this, _opts).getLocalMedia({ audio: false, video: true }).catch(() => void 0);
329
+ }
330
+ session.hadCamera = false;
331
+ await __privateMethod(this, _CallEngine_instances, setOutgoingVideoTrack_fn).call(this, session, (_a = cam == null ? void 0 : cam.getVideoTracks()[0]) != null ? _a : null, cam);
325
332
  await __privateMethod(this, _CallEngine_instances, renegotiate_fn).call(this, session);
326
333
  __privateMethod(this, _CallEngine_instances, log_fn).call(this, `shareScreen stopped for ${callId}`);
327
334
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/lan",
3
- "version": "0.1.178",
3
+ "version": "0.1.180",
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",
@@ -80,7 +80,7 @@
80
80
  "dependencies": {
81
81
  "@decentnetwork/dora": "^0.1.13",
82
82
  "@decentnetwork/peer": "^0.1.91",
83
- "@decentnetwork/peer-webrtc": "^0.2.9",
83
+ "@decentnetwork/peer-webrtc": "^0.2.10",
84
84
  "ink": "^5.2.1",
85
85
  "js-yaml": "^4.1.0",
86
86
  "node-forge": "^1.4.0",