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.
- package/README.md +6 -0
- package/package.json +1 -1
- 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
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 -
|
|
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(
|
|
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
|
});
|