windscout 0.6.4 → 0.7.0

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.
@@ -7,11 +7,13 @@ import { countryName, flag } from "./country.js";
7
7
  import { matrix, resultTable } from "./tables.js";
8
8
  import { draw, enterScreen, input, leaveScreen, out, repoLink } from "./terminal.js";
9
9
  import { bold, cyan, dim, green, red, vw, yellow } from "./text.js";
10
- import { bar, box, centerIn } from "./widgets.js";
10
+ import { bar, box, centerIn, spin } from "./widgets.js";
11
11
  /** Progress bar, current attempt, results so far. tab switches the table, p pauses, q stops (twice: quits).
12
12
  * blocking: we turned the firewall on for the scan */
13
13
  export function showProgress(scan, blocking) {
14
- const { protocols, options: { timeout, speedTest } } = scan;
14
+ const { protocols, options: { timeout } } = scan;
15
+ const speedTest = scan.options.speedTest || Boolean(scan.options.stopAtFirst); // stop at the first measures speed too
16
+ let quitting = false; // q pressed: a second one quits right away
15
17
  let view = speedTest ? "ranking" : "availability"; // tab switches
16
18
  let frame = 0;
17
19
  const render = () => {
@@ -22,7 +24,7 @@ export function showProgress(scan, blocking) {
22
24
  const partial = slots.reduce((sum, s) => sum + (s.current ? Math.min(0.95, (now() - s.attemptStart) / attemptSeconds(timeout)) : 0), 0);
23
25
  const fraction = (done + (done < total ? partial : 0)) / total;
24
26
  const eta = (total - done) * (done ? elapsed / done : attemptSeconds(timeout) / slots.length);
25
- const spinner = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"[frame++ % 10];
27
+ const spinner = spin(frame++);
26
28
  const ok = sortOk(results);
27
29
  // blocked: nothing worked, and something was really tried (a key limit or a port the app doesn't offer tries nothing)
28
30
  const failed = finished.filter((c) => !results.some((r) => r.query === c && r.status === "ok")
@@ -51,9 +53,10 @@ export function showProgress(scan, blocking) {
51
53
  : [];
52
54
  const width = Math.max(minW, table.length ? vw(table[0]) : 0);
53
55
  const inner = width - 4;
54
- const status = stopping ? yellow(` stopping after the current attempt${many ? "s" : ""}…`)
55
- : waiting ? yellow(` paused · VPN disconnected${blocking ? " · firewall blocking internet" : ""} · press p to resume`)
56
- : "";
56
+ const status = scan.found ? green(" ✓ a connection held up · stopping the other attempts…")
57
+ : stopping ? yellow(` stopping after the current attempt${many ? "s" : ""}…`)
58
+ : waiting ? yellow(` paused · VPN disconnected${blocking ? " · firewall blocking internet" : ""} · press p to resume`)
59
+ : "";
57
60
  const pausing = paused && !waiting && frame % 8 < 5;
58
61
  const slotLine = (s) => {
59
62
  if (!s.current)
@@ -102,12 +105,13 @@ export function showProgress(scan, blocking) {
102
105
  render();
103
106
  }
104
107
  else if (key.name === "q" || key.name === "escape" || (key.ctrl && key.name === "c")) {
105
- if (scan.stopping) { // second press: bail out now
108
+ if (quitting) { // second press: bail out now
106
109
  leaveScreen();
107
110
  console.error(`stopped. the VPN may still be connecting: run '${CLI} disconnect'`);
108
111
  await restoreFirewall();
109
112
  process.exit(130);
110
113
  }
114
+ quitting = true;
111
115
  scan.stop();
112
116
  render();
113
117
  }
@@ -126,11 +130,13 @@ const clip = (s, n) => (s.length > n ? s.slice(0, Math.max(0, n - 1)) + "…" :
126
130
  /** A line per attempt. Ctrl-C stops after the current attempt, a second one quits */
127
131
  export function logProgress(scan) {
128
132
  const many = scan.slots.length > 1; // attempts overlap: a whole line once each is done
133
+ let quitting = false; // Ctrl-C pressed: a second one quits right away
129
134
  process.on("SIGINT", async () => {
130
- if (scan.stopping) {
135
+ if (quitting) {
131
136
  await restoreFirewall();
132
137
  process.exit(130);
133
138
  }
139
+ quitting = true;
134
140
  scan.stop();
135
141
  });
136
142
  return {
@@ -14,7 +14,7 @@ const resultRow = (r, i) => [
14
14
  r.connectS === null ? "-" : `${fmt(r.connectS, 1)} s`,
15
15
  r.latencyMs === null ? "-" : `${fmt(r.latencyMs, 0)} ms`,
16
16
  r.speedMbps === null ? "-" : bold(`${fmt(r.speedMbps, 1)} Mbps`),
17
- r.status === "ok" ? green("✓ ok") : r.status === "key limit" || r.status === "no such port" ? yellow(r.status) : red(r.status),
17
+ r.status === "ok" ? green("✓ ok") : ["key limit", "no such port", "unreliable"].includes(r.status) ? yellow(r.status) : red(r.status),
18
18
  ];
19
19
  /** Numbered attempts, in the given order; the Speed column only when the speed test ran */
20
20
  export function resultTable(results, speedTest, options) {
@@ -13,6 +13,8 @@ export const input = new PassThrough();
13
13
  emitKeypressEvents(input);
14
14
  let fullscreen = false;
15
15
  let link = null; // where draw() last put the repo link
16
+ let waiting; // drawUntilNext()'s timer
17
+ let redrawing = false; // drawUntilNext()'s own redraw is drawing
16
18
  const openUrl = (url) => spawn({ darwin: "open", win32: "explorer" }[process.platform] ?? "xdg-open", [url], { stdio: "ignore", detached: true })
17
19
  .on("error", () => { }).unref();
18
20
  // SGR mouse report: ESC [ < button ; col ; row (M = press, m = release)
@@ -40,6 +42,7 @@ export function enterScreen() {
40
42
  }
41
43
  /** Back to the normal screen; drops every key and mouse listener */
42
44
  export function leaveScreen() {
45
+ clearInterval(waiting);
43
46
  if (!fullscreen)
44
47
  return;
45
48
  fullscreen = false;
@@ -51,8 +54,26 @@ export function leaveScreen() {
51
54
  out.write("\x1b[?1000l\x1b[?1006l\x1b[?25h\x1b[?1049l");
52
55
  }
53
56
  process.on("exit", leaveScreen);
57
+ /** Calls redraw every 120 ms (for a spinner) while the screen waits for the next one: until another screen draws, or leaveScreen() */
58
+ export function drawUntilNext(redraw) {
59
+ const tick = () => {
60
+ redrawing = true;
61
+ try {
62
+ redraw();
63
+ }
64
+ finally {
65
+ redrawing = false;
66
+ }
67
+ };
68
+ clearInterval(waiting);
69
+ waiting = setInterval(tick, 120);
70
+ waiting.unref(); // never what keeps the process alive
71
+ tick();
72
+ }
54
73
  /** Redraws in place, centered: home, each line cleared to its end, then clear below (no flicker). Returns the top-left corner */
55
74
  export function draw(lines) {
75
+ if (!redrawing)
76
+ clearInterval(waiting); // another screen: it takes over from drawUntilNext()
56
77
  const cols = out.columns || 80, rows = out.rows || 24;
57
78
  const w = Math.max(...lines.map(vw));
58
79
  const left = " ".repeat(Math.max(0, Math.floor((cols - w) / 2)));
@@ -32,6 +32,8 @@ export function grid(cols, rows, { caption, captionRight = "", minWidth = 0, str
32
32
  out.push(row(cols.map((c) => bold(c.title))), rule("├", "┼", "┤"), ...rows.map(row), rule("╰", "┴", "╯"));
33
33
  return out;
34
34
  }
35
+ /** A spinner's character for this frame */
36
+ export const spin = (frame) => "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"[frame % 10];
35
37
  /** s, indented to sit centered in width columns */
36
38
  export const centerIn = (s, width) => " ".repeat(Math.max(0, Math.floor((width - vw(s)) / 2))) + s;
37
39
  /** Gradient bar (cyan → green) with eighth-block precision on the leading edge */
package/dist/worker.js CHANGED
@@ -55,9 +55,9 @@ export async function worker() {
55
55
  if (!m.probe)
56
56
  continue;
57
57
  stop = false;
58
- const { query, protocol, timeout, speedTest } = m.probe;
58
+ const { query, protocol, timeout, speedTest, steady } = m.probe;
59
59
  const result = await probe(query, protocol, {
60
- timeout, speedTest,
60
+ timeout, speedTest, steady,
61
61
  stopped: () => stop,
62
62
  onStep: (step) => send({ step }),
63
63
  measure: async (fn) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "windscout",
3
- "version": "0.6.4",
4
- "description": "Find which Windscribe locations and protocols work from your network, ranked by latency (unofficial)",
3
+ "version": "0.7.0",
4
+ "description": "Find which Windscribe locations and protocols work from your network, ranked by speed (unofficial)",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "windscout": "dist/cli.js"
@@ -25,12 +25,14 @@
25
25
  "keywords": [
26
26
  "windscribe",
27
27
  "vpn",
28
+ "speedtest",
28
29
  "wireguard",
29
30
  "stealth",
30
31
  "cli",
31
32
  "censorship",
32
33
  "firewall",
33
34
  "unblock",
35
+ "vpn-speed-test",
34
36
  "server-finder",
35
37
  "docker"
36
38
  ],