windscout 0.4.4 → 0.4.5

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.
Files changed (3) hide show
  1. package/README.md +6 -0
  2. package/package.json +1 -1
  3. package/windscout.js +31 -11
package/README.md CHANGED
@@ -51,6 +51,7 @@ Run `windscout` with no arguments to open the interactive picker:
51
51
  | `a` / `n` | select all / unselect all (only the matching countries while a search is active) |
52
52
  | `1`–`6` | toggle a protocol (`1`–`5` on Linux, which has no IKEv2) |
53
53
  | `s` | toggle the speed test (off = connect-only mode) |
54
+ | `f` | toggle "block internet while scanning" (firewall) |
54
55
  | `enter` | start scanning |
55
56
  | `q` | quit (during a scan: stop and show partial results; press again to force quit) |
56
57
 
@@ -77,6 +78,7 @@ windscout -t 40 -a # longer connect timeout, also list
77
78
  | `-t, --timeout` | seconds to wait for each connection | 20 |
78
79
  | `-a, --all` | include failed attempts in the final table | off |
79
80
  | `-n, --no-speed` | connect-only mode: skip the download test, rank by latency | picker: saved, or connect-only on first run; with `-c`: speed test on |
81
+ | `-f, --firewall` | block internet while scanning, except through the VPN | picker: saved, or off; with `-c`: off |
80
82
  | `-v, --version` | print the installed version, and the update command if a newer one is on npm | |
81
83
 
82
84
  ## Connect-only mode
@@ -100,6 +102,10 @@ Both modes show an **availability grid**: one row per country, and ✓ / ✗ for
100
102
 
101
103
  While scanning, press `tab` to switch between the availability grid and the speed or latency ranking. Press `p` (or `space`) to pause: the current attempt finishes, the VPN disconnects, and the scan waits until you press `p` again. Paused time doesn't count toward elapsed or remaining. In connect-only mode the scan starts on the availability grid.
102
104
 
105
+ ## Block internet while scanning (firewall)
106
+
107
+ Between attempts the VPN is disconnected for a moment, and apps on your computer (browser, messengers, updates) can go online over your real connection. Turn on **block internet while scanning** with `f` in the picker or `--firewall`, and WindScout switches on the Windscribe app's firewall for the scan: nothing gets online except through the VPN. When the scan ends or you stop it, the firewall goes back off (if you already had it on, WindScout leaves it on). While a scan is paused, the firewall keeps blocking.
108
+
103
109
  ## Output
104
110
 
105
111
  While scanning:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "windscout",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "Find which Windscribe locations and protocols work from your network, ranked by speed (unofficial)",
5
5
  "type": "module",
6
6
  "bin": {
package/windscout.js CHANGED
@@ -47,6 +47,8 @@ const noFlags = process.platform === "win32" || process.env.TERM_PROGRAM === "Ap
47
47
  const flag = (code) => !noFlags && /^[A-Z]{2}$/.test(code) ? String.fromCodePoint(...[...code].map((c) => 0x1f1a5 + c.charCodeAt(0))) : " ";
48
48
  let stop = 0;
49
49
  let speedTest = true; // false = connect-only mode: skip the download test, rank by latency
50
+ let firewall = false; // turn on the app's firewall while scanning, so nothing goes online outside the VPN
51
+ let restoreFirewall = async () => {}; // set by scan(); also called on a crash so the internet isn't left blocked
50
52
 
51
53
  // ---------- windscribe + measurements ----------
52
54
 
@@ -314,6 +316,7 @@ function loadConfig() {
314
316
  countries: (c.countries ?? []).filter((x) => COUNTRIES.includes(x)),
315
317
  protocols: (c.protocols ?? []).filter((x) => PROTOCOLS.includes(x)),
316
318
  speed: c.speed !== false,
319
+ firewall: c.firewall === true,
317
320
  };
318
321
  } catch {
319
322
  return null; // first run or unreadable file
@@ -415,7 +418,7 @@ function pick(saved, timeout) {
415
418
  const render = () => {
416
419
  const rows = out.rows || 24;
417
420
  const list = visible();
418
- const height = Math.max(3, rows - 19 - (update ? 1 : 0));
421
+ const height = Math.max(3, rows - 20 - (update ? 1 : 0));
419
422
  cursor = Math.max(0, Math.min(cursor, list.length - 1));
420
423
  if (cursor < top) top = cursor;
421
424
  if (cursor >= top + height) top = cursor - height + 1;
@@ -426,6 +429,10 @@ function pick(saved, timeout) {
426
429
  const speedLine = `${dim(under("s"))} ${speedTest ? green("● speed test") : dim("○ speed test")} ${dim(speedTest
427
430
  ? "ranks by download speed · adds ~15s per working connection"
428
431
  : "off: connect-only, ranks by latency")}`;
432
+ const fwLabel = "block internet while scanning (firewall)";
433
+ const fwLine = `${dim(under("f"))} ${firewall ? green("● " + fwLabel) : dim("○ " + fwLabel)} ${dim(firewall
434
+ ? "only VPN traffic gets through"
435
+ : "off: apps may leak in between")}`;
429
436
  const search = searching
430
437
  ? `${cyan("🔍")} ${bold(query)}${cyan("▏")}`
431
438
  : query ? `🔍 ${query}` : dim("🔍 press / to search");
@@ -442,9 +449,9 @@ function pick(saved, timeout) {
442
449
  const buttonLine = buttons.map(([id, label]) => (id === "start" ? bold(cyan(`[ ${label} ]`)) : dim(`[ ${label} ]`))).join(" ");
443
450
 
444
451
  const table = grid([{ title: "" }, { title: "Code" }, { title: "Country" }], body,
445
- { caption: search, captionRight: dim(range), minWidth: Math.max(vw(protoLine), vw(speedLine)) + 4 });
452
+ { caption: search, captionRight: dim(range), minWidth: Math.max(vw(protoLine), vw(speedLine), vw(fwLine)) + 4 });
446
453
  const width = vw(table[0]);
447
- const head = box([protoLine, speedLine], {
454
+ const head = box([protoLine, speedLine, fwLine], {
448
455
  title: bold(cyan("WindScout")),
449
456
  right: `${bold(String(selected.size))}${dim(`/${COUNTRIES.length} selected`)}`,
450
457
  width: width - 4,
@@ -460,7 +467,7 @@ function pick(saved, timeout) {
460
467
  centerIn(dim(searching
461
468
  ? "type to filter · ↑↓ move · enter/esc done"
462
469
  : `↑↓ move · space select · a/n ${query ? "all/none shown" : "all/none"} · / search`), width),
463
- centerIn(dim(searching ? "" : `1-${PROTOCOLS.length} protocols · s speed test · enter start · ${query ? "esc clear · " : ""}q quit`), width),
470
+ centerIn(dim(searching ? "" : `1-${PROTOCOLS.length} protocols · s speed test · f firewall · enter start · ${query ? "esc clear · " : ""}q quit`), width),
464
471
  ];
465
472
  lines.splice(head.length + table.length + 1, 0, centerIn(buttonLine, width), "");
466
473
  if (message) lines.push(centerIn(yellow(message), width));
@@ -485,7 +492,7 @@ function pick(saved, timeout) {
485
492
  return range;
486
493
  });
487
494
  hit = {
488
- protoRow: y0 + 1, speedRow: y0 + 2, protoHits,
495
+ protoRow: y0 + 1, speedRow: y0 + 2, firewallRow: y0 + 3, protoHits,
489
496
  searchRow: y0 + head.length + 1,
490
497
  firstRow: y0 + head.length + 5, shown: shown.length,
491
498
  buttonRow: y0 + head.length + table.length + 1, buttonHits,
@@ -510,6 +517,7 @@ function pick(saved, timeout) {
510
517
  countries: COUNTRIES.filter((c) => selected.has(c)),
511
518
  protocols: PROTOCOLS.filter((p) => protos.has(p)),
512
519
  speed: speedTest,
520
+ firewall,
513
521
  };
514
522
  saveConfig(choice);
515
523
  finish(choice);
@@ -535,6 +543,7 @@ function pick(saved, timeout) {
535
543
  const i = hit.protoHits.findIndex(([a, b]) => x >= a && x < b);
536
544
  if (i >= 0) protos.has(PROTOCOLS[i]) ? protos.delete(PROTOCOLS[i]) : protos.add(PROTOCOLS[i]);
537
545
  } else if (y === hit.speedRow) speedTest = !speedTest;
546
+ else if (y === hit.firewallRow) firewall = !firewall;
538
547
  else if (y === hit.buttonRow) {
539
548
  const id = hit.buttonHits.find(([, a, b]) => x >= a && x < b)?.[0];
540
549
  if (id === "start" && start()) return;
@@ -578,6 +587,7 @@ function pick(saved, timeout) {
578
587
  else if (k === "a") list.forEach((c) => selected.add(c));
579
588
  else if (k === "n") list.forEach((c) => selected.delete(c));
580
589
  else if (k === "s") speedTest = !speedTest;
590
+ else if (k === "f") firewall = !firewall;
581
591
  else if (/^[1-9]$/.test(str ?? "") && PROTOCOLS[Number(str) - 1]) {
582
592
  const p = PROTOCOLS[Number(str) - 1];
583
593
  protos.has(p) ? protos.delete(p) : protos.add(p);
@@ -601,6 +611,10 @@ async function scan(countries, protocols, timeout, tty) {
601
611
  let view = speedTest ? "ranking" : "availability"; // tab switches
602
612
  // pause lands between attempts, so the VPN is disconnected while waiting; idle = seconds spent paused
603
613
  let paused = false, waiting = false, waitStart = 0, idle = 0, resume = () => {};
614
+ // firewall: block everything outside the VPN while switching locations; put it back the way the user had it
615
+ const fwChanged = firewall && /^Firewall state: Off/m.test(await cli("status"));
616
+ if (fwChanged) await cli("firewall", "on");
617
+ restoreFirewall = async () => { if (fwChanged) await cli("firewall", "off"); };
604
618
 
605
619
  const render = () => {
606
620
  const rows = out.rows || 24;
@@ -642,9 +656,9 @@ async function scan(countries, protocols, timeout, tty) {
642
656
  "",
643
657
  stop
644
658
  ? yellow(" stopping after the current attempt…")
645
- : waiting ? yellow(" paused · VPN disconnected · press p to resume")
659
+ : waiting ? yellow(` paused · VPN disconnected${fwChanged ? " · firewall blocking internet" : ""} · press p to resume`)
646
660
  : ` ${cyan(spinner)} ${bold(current)}`,
647
- stop || waiting ? "" : ` ${dim(step)} ${dim("·")} ${duration(now() - attemptStart)}${paused ? yellow(" · pausing after this attempt") : ""}`,
661
+ stop || waiting ? "" : ` ${dim(step)} ${dim("·")} ${duration(now() - attemptStart)}${paused && frame % 8 < 5 ? yellow(" · pausing after this attempt") : ""}`,
648
662
  null,
649
663
  centerIn(stats, inner),
650
664
  ], {
@@ -668,7 +682,7 @@ async function scan(countries, protocols, timeout, tty) {
668
682
  let timer;
669
683
  if (tty) {
670
684
  if (!fullscreen) enterScreen();
671
- input.on("keypress", (str, key = {}) => {
685
+ input.on("keypress", async (str, key = {}) => {
672
686
  if (key.name === "tab") {
673
687
  view = view === "ranking" ? "availability" : "ranking";
674
688
  render();
@@ -680,6 +694,7 @@ async function scan(countries, protocols, timeout, tty) {
680
694
  if (stop++) { // second press: bail out now
681
695
  leaveScreen();
682
696
  console.error(`stopped. the VPN may still be connecting: run '${CLI} disconnect'`);
697
+ await restoreFirewall();
683
698
  process.exit(130);
684
699
  }
685
700
  resume(); // a paused scan stops right away
@@ -689,7 +704,7 @@ async function scan(countries, protocols, timeout, tty) {
689
704
  out.on("resize", render);
690
705
  timer = setInterval(render, 120);
691
706
  } else {
692
- process.on("SIGINT", () => { if (stop++) process.exit(130); });
707
+ process.on("SIGINT", async () => { if (stop++) await restoreFirewall(), process.exit(130); });
693
708
  }
694
709
 
695
710
  outer: for (const loc of countries) {
@@ -711,6 +726,7 @@ async function scan(countries, protocols, timeout, tty) {
711
726
  }
712
727
  finishedCountries.push(loc); // table/failed list refresh per finished country
713
728
  }
729
+ await restoreFirewall();
714
730
 
715
731
  if (tty) {
716
732
  clearInterval(timer);
@@ -780,6 +796,7 @@ Options:
780
796
  -t, --timeout SEC seconds to wait for each connection (default: 20)
781
797
  -a, --all also show failed attempts in the final table
782
798
  -n, --no-speed connect-only: skip the download test, rank by latency (faster scan)
799
+ -f, --firewall block internet while scanning, except through the VPN (turned back off afterwards)
783
800
  -v, --version show the installed version (and whether a newer one is out)
784
801
  -h, --help show this help
785
802
 
@@ -796,6 +813,7 @@ async function main() {
796
813
  help: { type: "boolean", short: "h", default: false },
797
814
  version: { type: "boolean", short: "v", default: false },
798
815
  "no-speed": { type: "boolean", short: "n", default: false },
816
+ firewall: { type: "boolean", short: "f", default: false },
799
817
  },
800
818
  });
801
819
  if (a.help) return console.log(HELP);
@@ -823,13 +841,14 @@ async function main() {
823
841
  const saved = loadConfig();
824
842
  if (protocols && saved) saved.protocols = protocols.filter((p) => PROTOCOLS.includes(p));
825
843
  speedTest = !a["no-speed"] && (saved?.speed ?? false); // first run: connect-only
844
+ firewall = a.firewall || (saved?.firewall ?? false);
826
845
  const choice = await pick(saved, timeout);
827
846
  if (!choice) return;
828
847
  countries = choice.countries;
829
848
  protocols = protocols ?? choice.protocols;
830
849
  }
831
850
  protocols ??= PROTOCOLS;
832
- if (a.countries) speedTest = !a["no-speed"];
851
+ if (a.countries) speedTest = !a["no-speed"], firewall = a.firewall;
833
852
 
834
853
  const results = await scan(countries, protocols, timeout, tty);
835
854
  if (stop) console.log(yellow("interrupted, showing partial results"));
@@ -838,8 +857,9 @@ async function main() {
838
857
  if (update) console.error("\n" + yellow(updateNotice()));
839
858
  }
840
859
 
841
- main().catch((e) => {
860
+ main().catch(async (e) => {
842
861
  leaveScreen();
843
862
  console.error(`windscout: ${e.message}`);
863
+ await restoreFirewall().catch(() => console.error(`the firewall may still be on: run '${CLI} firewall off'`));
844
864
  process.exit(1);
845
865
  });