@decentnetwork/lan 0.1.195 → 0.1.196

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
@@ -1423,8 +1423,9 @@ export async function cmdProxyTrustCa(args) {
1423
1423
  if (process.platform === "linux") {
1424
1424
  // WSL: the browser runs on the WINDOWS side and trusts the WINDOWS cert
1425
1425
  // store — installing into this distro's Linux store does nothing for it.
1426
- // certutil.exe is callable from WSL and the per-user ROOT store needs no
1427
- // admin (Windows pops a confirmation dialog the user must accept).
1426
+ // certutil -user -addstore ROOT is rejected on modern Windows
1427
+ // (0x80070032 ERROR_NOT_SUPPORTED); PowerShell Import-Certificate into
1428
+ // CurrentUser\Root works without admin (Windows pops a confirmation dialog).
1428
1429
  const isWsl = existsSync("/proc/version") &&
1429
1430
  readFileSync("/proc/version", "utf-8").toLowerCase().includes("microsoft");
1430
1431
  if (isWsl) {
@@ -1434,19 +1435,20 @@ export async function cmdProxyTrustCa(args) {
1434
1435
  try {
1435
1436
  copyFileSync(caCertPath, winCopy);
1436
1437
  console.log("A Windows security dialog will pop up — click Yes to trust the CA...");
1437
- const w = spawnSync("certutil.exe", ["-user", "-addstore", "-f", "ROOT", winPath], { stdio: "inherit" });
1438
+ const w = spawnSync("powershell.exe", ["-NoProfile", "-Command", `Import-Certificate -FilePath '${winPath}' -CertStoreLocation Cert:\\CurrentUser\\Root`], { stdio: "inherit" });
1438
1439
  if (w.status === 0) {
1439
1440
  console.log("✓ Windows store updated. Chrome/Edge on Windows pick it up immediately (reload the page).");
1440
1441
  }
1441
1442
  else {
1442
- console.log("Install failed or dialog declined — in a Windows terminal run:");
1443
- console.log(` certutil -user -addstore -f ROOT "${winPath}"`);
1443
+ console.log("Install failed or dialog declined — in a Windows PowerShell run:");
1444
+ console.log(` Import-Certificate -FilePath "${winPath}" -CertStoreLocation Cert:\\CurrentUser\\Root`);
1445
+ console.log(`(or, in an ADMIN terminal: certutil -addstore -f ROOT "${winPath}")`);
1444
1446
  }
1445
1447
  }
1446
1448
  catch (e) {
1447
1449
  console.log(`Could not copy the cert to the Windows side (${e.message}). Manually:`);
1448
1450
  console.log(` cp "${caCertPath}" ${winCopy}`);
1449
- console.log(` certutil.exe -user -addstore -f ROOT "${winPath}"`);
1451
+ console.log(` then in Windows PowerShell: Import-Certificate -FilePath "${winPath}" -CertStoreLocation Cert:\\CurrentUser\\Root`);
1450
1452
  }
1451
1453
  console.log("(Linux-side clients like curl inside WSL would additionally need the system CA store:");
1452
1454
  console.log(` sudo cp "${caCertPath}" /usr/local/share/ca-certificates/agentnet-mitm-ca.crt && sudo update-ca-certificates)`);
@@ -1464,13 +1466,14 @@ export async function cmdProxyTrustCa(args) {
1464
1466
  }
1465
1467
  if (process.platform === "win32") {
1466
1468
  console.log("Installing into the Windows user trust store (a security dialog will pop up — click Yes)...");
1467
- const w = spawnSync("certutil", ["-user", "-addstore", "-f", "ROOT", caCertPath], { stdio: "inherit" });
1469
+ const w = spawnSync("powershell.exe", ["-NoProfile", "-Command", `Import-Certificate -FilePath '${caCertPath}' -CertStoreLocation Cert:\\CurrentUser\\Root`], { stdio: "inherit" });
1468
1470
  if (w.status === 0) {
1469
1471
  console.log("✓ Trusted. Chrome/Edge pick it up immediately (reload the page).");
1470
1472
  }
1471
1473
  else {
1472
- console.log("Install failed or dialog declined — run manually (admin terminal for the machine store):");
1473
- console.log(` certutil -addstore -f ROOT "${caCertPath}"`);
1474
+ console.log("Install failed or dialog declined — in PowerShell run:");
1475
+ console.log(` Import-Certificate -FilePath "${caCertPath}" -CertStoreLocation Cert:\\CurrentUser\\Root`);
1476
+ console.log(`(or, in an ADMIN terminal: certutil -addstore -f ROOT "${caCertPath}")`);
1474
1477
  }
1475
1478
  return;
1476
1479
  }
@@ -116,6 +116,11 @@ const EXIT_CONNECT_TIMEOUT_MS = 4000;
116
116
  // underestimates by ~5x and poisons the ordering. Video segments clear this;
117
117
  // API calls and redirects don't (they stay unmeasured = balanced by load).
118
118
  const SPEED_MIN_BYTES = 512 * 1024;
119
+ // The 600 kbps CCTV fallback produces ~390-430 KB four-second segments. They
120
+ // are large enough to rank an exit, but never crossed the generic 512 KB gate,
121
+ // leaving every exit "unproven" and rotating foreground playback across slow
122
+ // paths. Keep the generic gate conservative and use this only for HLS bodies.
123
+ const HLS_SPEED_MIN_BYTES = 256 * 1024;
119
124
  // We record PEAK 1-second throughput within a transfer, not lifetime average,
120
125
  // so slow-start ramp-up doesn't drag the estimate down.
121
126
  const SPEED_WINDOW_MS = 1000;
@@ -159,6 +164,11 @@ const NEAR_DEAD_EXIT_BPS = 60_000;
159
164
  // threshold admitted 10.86.15.133 on a short peak even though its full CCTV
160
165
  // segments averaged only 0.7-1.2 Mbit/s, causing each half-range to take 4-6s.
161
166
  const HLS_DELIVERY_BPS = 200_000;
167
+ // Below this speed even the capped 0.9 Mbps stream has no useful headroom. If
168
+ // unproven exits remain, keep exploring them instead of locking startup onto
169
+ // the first measured-but-dead path (observed: 0.3 Mbps 15.133 hid the actually
170
+ // usable 134.139 for the remainder of the router process).
171
+ const HLS_STARTUP_EXPLORE_BPS = 100_000;
162
172
  // Stability-first default for constrained exit pools. Set 0 to expose every
163
173
  // origin variant, or 1800000 to restore CCTV 720P when another fast exit joins.
164
174
  const HLS_MAX_VARIANT_BPS = process.env.AGENTNET_HLS_MAX_BPS === undefined
@@ -235,12 +245,14 @@ function floorExit(exit) {
235
245
  /** Feed one tunnel's rate meter into the exit's speed EWMA (peak-window based).
236
246
  * Only transfers past SPEED_MIN_BYTES count — smaller ones never reveal path
237
247
  * capacity on a high-RTT link. */
238
- function sampleSpeed(exit, meter, durMs, recoveryMinBps = NEAR_DEAD_EXIT_BPS) {
248
+ function sampleSpeed(exit, meter, durMs, recoveryMinBps = NEAR_DEAD_EXIT_BPS, minBytes = SPEED_MIN_BYTES, useWholeAverage = false) {
239
249
  exit.bytesDown += meter.total;
240
- if (meter.total < SPEED_MIN_BYTES)
250
+ if (meter.total < minBytes)
241
251
  return;
242
- const bps = meter.peakBps(durMs);
243
- if (bps <= 0)
252
+ const peakBps = meter.peakBps(durMs);
253
+ const averageBps = durMs > 0 ? (meter.total / durMs) * 1000 : 0;
254
+ const bps = useWholeAverage ? Math.min(peakBps, averageBps) : peakBps;
255
+ if (bps <= 0 || averageBps <= 0)
244
256
  return;
245
257
  if (exit.flooredAtMs !== null) {
246
258
  // FLOOR_BPS is a penalty, not a real historical sample. A qualifying probe
@@ -251,8 +263,7 @@ function sampleSpeed(exit, meter, durMs, recoveryMinBps = NEAR_DEAD_EXIT_BPS) {
251
263
  // also prove whole-segment average throughput. A probe observed at a 0.9
252
264
  // Mbit/s peak but only 0.6 Mbit/s average caused six-way prefetch/hedge
253
265
  // congestion when it rejoined a 1.8 Mbit/s stream.
254
- const averageBps = durMs > 0 ? (meter.total / durMs) * 1000 : 0;
255
- if (isUsefulRecoverySample(bps, averageBps, recoveryMinBps)) {
266
+ if (isUsefulRecoverySample(peakBps, averageBps, recoveryMinBps)) {
256
267
  exit.speedEwma = bps;
257
268
  exit.flooredAtMs = null;
258
269
  }
@@ -287,10 +298,14 @@ export function selectHlsPrimaryPool(pool) {
287
298
  // still safer than falling back to every unproven/degraded exit. This is the
288
299
  // adaptive-bitrate path: the master playlist is capped separately, while
289
300
  // foreground delivery stays on the least-bad known tier.
301
+ const unproven = pool.filter((e) => e.flooredAtMs === null && e.speedEwma === null);
290
302
  const measured = pool.filter((e) => e.flooredAtMs === null && e.speedEwma !== null);
291
- if (measured.length > 0)
303
+ if (measured.length > 0) {
304
+ const bestMeasured = Math.max(...measured.map((e) => e.speedEwma ?? 0));
305
+ if (bestMeasured < HLS_STARTUP_EXPLORE_BPS && unproven.length > 0)
306
+ return unproven;
292
307
  return fastestTier(measured);
293
- const unproven = pool.filter((e) => e.flooredAtMs === null && e.speedEwma === null);
308
+ }
294
309
  return unproven.length > 0 ? unproven : pool;
295
310
  }
296
311
  /** Dynamic prefetch concurrency follows only the foreground-safe pool. */
@@ -1315,7 +1330,7 @@ export function startMultiExitRouter(opts) {
1315
1330
  nextExit(`empty ${status}`);
1316
1331
  return;
1317
1332
  }
1318
- sampleSpeed(exit, meter, durMs, HLS_DELIVERY_BPS);
1333
+ sampleSpeed(exit, meter, durMs, HLS_DELIVERY_BPS, HLS_SPEED_MIN_BYTES, true);
1319
1334
  if (label) {
1320
1335
  const kb = (body.length / 1024).toFixed(0);
1321
1336
  const mbps = durMs > 0 ? ((body.length * 8) / durMs / 1000).toFixed(1) : "?";
@@ -1391,7 +1406,7 @@ export function startMultiExitRouter(opts) {
1391
1406
  return false;
1392
1407
  const now = Date.now();
1393
1408
  return exits.some((e) => e.region === fallbackRegion && e.trippedUntil <= now &&
1394
- e.flooredAtMs === null && e.speedEwma !== null);
1409
+ e.flooredAtMs === null && e.speedEwma !== null && e.speedEwma >= HLS_STARTUP_EXPLORE_BPS);
1395
1410
  };
1396
1411
  // Stripe count is a connection count, not a distinct-exit count. Keep three
1397
1412
  // parallel ranges even when only one exit is qualified: two ranges still
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/lan",
3
- "version": "0.1.195",
3
+ "version": "0.1.196",
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",