windscout 0.4.2 → 0.4.4

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 CHANGED
@@ -98,7 +98,7 @@ Both modes show an **availability grid**: one row per country, and ✓ / ✗ for
98
98
  ╰─────────────────────────┴────────────────────────┴─────────┴───────────┴─────╯
99
99
  ```
100
100
 
101
- While scanning, press `tab` to switch between the availability grid and the speed or latency ranking. In connect-only mode the scan starts on the availability grid.
101
+ 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
102
 
103
103
  ## Output
104
104
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "windscout",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
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
@@ -150,6 +150,9 @@ async function probe(query, protocol, timeout, onStep = () => {}) {
150
150
  } finally {
151
151
  onStep("disconnecting");
152
152
  await cli("disconnect");
153
+ // the CLI's disconnect can return while the app is still tearing down (it does after a timed-out connect);
154
+ // a connect sent then is swallowed and the next attempt reads as an instant "failed", so wait it out
155
+ for (let i = 0; i < 30 && (await state()) === "Disconnecting"; i++) await sleep(1000);
153
156
  }
154
157
  }
155
158
 
@@ -596,12 +599,14 @@ async function scan(countries, protocols, timeout, tty) {
596
599
  const started = now();
597
600
  let done = 0, attemptStart = now(), current = "", step = "", frame = 0, finishedCountries = [];
598
601
  let view = speedTest ? "ranking" : "availability"; // tab switches
602
+ // pause lands between attempts, so the VPN is disconnected while waiting; idle = seconds spent paused
603
+ let paused = false, waiting = false, waitStart = 0, idle = 0, resume = () => {};
599
604
 
600
605
  const render = () => {
601
606
  const rows = out.rows || 24;
602
- const elapsed = now() - started;
607
+ const elapsed = now() - started - idle - (waiting ? now() - waitStart : 0);
603
608
  // smooth bar: count the running attempt as partially done
604
- const partial = Math.min(0.95, (now() - attemptStart) / attemptSeconds(timeout));
609
+ const partial = waiting ? 0 : Math.min(0.95, (now() - attemptStart) / attemptSeconds(timeout));
605
610
  const fraction = (done + (done < total ? partial : 0)) / total;
606
611
  const eta = (total - done) * (done ? elapsed / done : attemptSeconds(timeout));
607
612
  const spinner = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"[frame++ % 10];
@@ -637,8 +642,9 @@ async function scan(countries, protocols, timeout, tty) {
637
642
  "",
638
643
  stop
639
644
  ? yellow(" stopping after the current attempt…")
645
+ : waiting ? yellow(" paused · VPN disconnected · press p to resume")
640
646
  : ` ${cyan(spinner)} ${bold(current)}`,
641
- stop ? "" : ` ${dim(step)} ${dim("·")} ${duration(now() - attemptStart)}`,
647
+ stop || waiting ? "" : ` ${dim(step)} ${dim("·")} ${duration(now() - attemptStart)}${paused ? yellow(" · pausing after this attempt") : ""}`,
642
648
  null,
643
649
  centerIn(stats, inner),
644
650
  ], {
@@ -655,7 +661,7 @@ async function scan(countries, protocols, timeout, tty) {
655
661
  while (vw(txt) > width && failed.length) txt = txt.slice(0, -8) + "…"; // ponytail: crude trim, fine for a status line
656
662
  lines.push("", centerIn(txt, width));
657
663
  }
658
- lines.push("", centerIn(dim(`tab ${view === "ranking" ? "availability" : "ranking"} view · q stop · q twice force quit`), width), "", centerIn(dim(repoLink), width));
664
+ lines.push("", centerIn(dim(`tab ${view === "ranking" ? "availability" : "ranking"} view · p ${paused ? "resume" : "pause"} · q stop · q twice force quit`), width), "", centerIn(dim(repoLink), width));
659
665
  draw(lines);
660
666
  };
661
667
 
@@ -666,12 +672,17 @@ async function scan(countries, protocols, timeout, tty) {
666
672
  if (key.name === "tab") {
667
673
  view = view === "ranking" ? "availability" : "ranking";
668
674
  render();
675
+ } else if (key.name === "p" || key.name === "space") {
676
+ paused = !paused;
677
+ if (!paused) resume();
678
+ render();
669
679
  } else if (key.name === "q" || key.name === "escape" || (key.ctrl && key.name === "c")) {
670
680
  if (stop++) { // second press: bail out now
671
681
  leaveScreen();
672
682
  console.error(`stopped. the VPN may still be connecting: run '${CLI} disconnect'`);
673
683
  process.exit(130);
674
684
  }
685
+ resume(); // a paused scan stops right away
675
686
  render();
676
687
  }
677
688
  });
@@ -683,6 +694,11 @@ async function scan(countries, protocols, timeout, tty) {
683
694
 
684
695
  outer: for (const loc of countries) {
685
696
  for (const proto of protocols) {
697
+ if (paused && !stop) {
698
+ waiting = true, waitStart = now();
699
+ await new Promise((r) => (resume = r));
700
+ waiting = false, idle += now() - waitStart;
701
+ }
686
702
  if (stop) break outer;
687
703
  current = `${flag(loc)} ${countryName(loc)} ${dim("·")} ${proto}`;
688
704
  attemptStart = now();