windscout 0.7.0 → 0.9.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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Find which Windscribe locations and protocols actually work from your network, ranked by speed. Runs on Linux, macOS and Windows.
4
4
 
5
- WindScout drives the official `windscribe-cli`: it connects to each country × protocol pair, measures latency and download speed through the tunnel, disconnects, and prints a ranked table.
5
+ WindScout drives the official `windscribe-cli`: it connects to each country × protocol pair, measures latency and download speed through the tunnel, disconnects, and prints a ranked table. Then the [results screen](#results-screen-connect-test-again-stay-connected) stays open: connect to one, test them all again, and let WindScout keep you connected.
6
6
 
7
7
  ![WindScout picking countries, scanning, and printing the availability grid and ranked results](demo.gif)
8
8
 
@@ -12,7 +12,7 @@ WindScout drives the official `windscribe-cli`: it connects to each country × p
12
12
 
13
13
  - Linux, macOS or Windows 10/11
14
14
  - Node.js 20+
15
- - The [Windscribe desktop app](https://windscribe.com/download) installed, running and logged in. It includes `windscribe-cli`, which WindScout uses to control the app. Check with `windscribe-cli status`, which should show `Logged in`. On Windows the app doesn't put the CLI on `PATH`, so run `"C:\Program Files\Windscribe\windscribe-cli.exe" status` instead. WindScout finds it there on its own.
15
+ - The [Windscribe desktop app](https://windscribe.com/download) installed, running and logged in. It includes `windscribe-cli`, which WindScout uses to control the app. Check with `windscribe-cli status`, which should show `Logged in`. Any recent version works: 2.25 reworded that status (`Connection:` instead of `Connect state:`, among others), and WindScout reads both wordings. On Windows the app doesn't put the CLI on `PATH`, so run `"C:\Program Files\Windscribe\windscribe-cli.exe" status` instead. WindScout finds it there on its own.
16
16
  - Optional, for [parallel scans](#parallel-scan-with-docker): Docker. WindScout offers to install it.
17
17
 
18
18
  ## Install
@@ -108,6 +108,7 @@ Run `windscout` with no arguments to open the interactive picker:
108
108
  | `d` | toggle the [parallel scan in docker containers](#parallel-scan-with-docker) (installs or starts docker first, after asking) |
109
109
  | `o` | change the [scanning type](#scanning-type): default, by random country, by last success result first, only the last results |
110
110
  | `w` | toggle [stop at the first working connection](#stop-at-the-first-working-connection) that holds up |
111
+ | `h` | type how many seconds each working connection has to [hold up](#stop-at-the-first-working-connection) for `w` (enter/esc to finish) |
111
112
  | `u` | update to the newer version and restart (only when the picker shows one is out) |
112
113
  | `enter` | start scanning |
113
114
  | `q` | quit (during a scan: stop and show partial results; press again to force quit) |
@@ -116,7 +117,7 @@ Run `windscout` with no arguments to open the interactive picker:
116
117
 
117
118
  Your selection is saved to `~/.config/windscout/config.json` (`%USERPROFILE%\.config\windscout\config.json` on Windows) and preselected next time.
118
119
 
119
- After you start, the Start button spins (`[ ⠋ Starting… ]`) until the scan's screen shows, which can take a moment when it turns the firewall on or starts docker. While scanning, WindScout shows a progress bar, elapsed time and remaining time. It also shows what it's doing right now (connecting, measuring latency or measuring speed). The table of working locations refreshes after each country. When the scan finishes, the final report is printed to your normal terminal. It ends with the best connection and the other working ones, each with its `windscribe-cli connect` command. At the prompt, press enter to connect to the best one, type another one's number to connect to that, or `q` to quit.
120
+ After you start, the Start button spins (`[ ⠋ Starting… ]`) until the scan's screen shows, which can take a moment when it turns the firewall on or starts docker. While scanning, WindScout shows a progress bar, elapsed time and remaining time. It also shows what it's doing right now (connecting, measuring latency or measuring speed). The table of working locations refreshes after each country. When the scan finishes, the final report is printed to your normal terminal. It ends with the best connection and the other working ones, each with its `windscribe-cli connect` command. Then the [results screen](#results-screen-connect-test-again-stay-connected) opens over it, and WindScout stays open until you quit. The report is still there in your terminal afterwards.
120
121
 
121
122
  ### Non-interactive
122
123
 
@@ -140,18 +141,88 @@ windscout -t 40 -a # longer connect timeout, also list
140
141
  | `-f, --firewall` | block internet while scanning, except through the VPN | picker: saved, or off; with `-c`: off |
141
142
  | `-o, --order` | [scanning type](#scanning-type): `default`, `random`, `last-first` or `last-only` (`-r` still works: the same as `-o random`) | picker: saved, or `default`; with `-c`: `default` |
142
143
  | `-w, --stop-at-first` | [stop at the first working connection](#stop-at-the-first-working-connection) that holds up | picker: saved, or off; with `-c`: off |
144
+ | `-H, --hold` | with `-w`: seconds each working connection has to [hold up](#stop-at-the-first-working-connection), at least 10 | picker: saved, or 30; with `-c`: 30 |
143
145
  | `-d, --docker` | [scan in parallel, in docker containers](#parallel-scan-with-docker) | picker: saved, or off; with `-c`: off |
144
146
  | `-v, --version` | print the installed version; if a newer one is on npm, show the update command (with `sudo` when needed) and ask (y/n) to run it | |
145
147
 
148
+ Without a terminal, WindScout prints the report and exits, with no results screen.
149
+
150
+ ## Results screen: connect, test again, stay connected
151
+
152
+ In a terminal, the scan's working connections stay on screen after the report, best first:
153
+
154
+ ```
155
+ ╭─ WindScout ─────────────────────────────────────────────────── 4 working · scanned 2m ago ─╮
156
+ │ │
157
+ │ ● CONNECTED 🇳🇱 Amsterdam - Canal over stealth:443 │
158
+ │ up 12m 07s · 106 ms ▄██▁▂▁ · IP 185.x.x.x · connected again 1× │
159
+ │ │
160
+ ├────────────────────────────────────────────────────────────────────────────────────────────┤
161
+ │ ◉ watch on · when this connection drops, windscout connects you to the next best │
162
+ │ ◉ background checks · 3 of 3 worked 2m ago · next in 3m 00s │
163
+ ╰────────────────────────────────────────────────────────────────────────────────────────────╯
164
+ ╭────────────────────────────────────────────────────────────────────────────────────────────╮
165
+ │ Working connections by speed · o: by latency │
166
+ ├─────┬─────────────────────────────┬─────────────┬─────────┬───────────┬────────────────────┤
167
+ │ # │ Location │ Protocol │ Latency │ Speed │ Status │
168
+ ├─────┼─────────────────────────────┼─────────────┼─────────┼───────────┼────────────────────┤
169
+ │ ❯ 1 │ 🇩🇪 Frankfurt - Castle │ wireguard │ 105 ms │ 48.2 Mbps │ ✓ 2m ago │
170
+ │ 2 │ 🇳🇱 Amsterdam - Canal │ stealth:443 │ 107 ms │ 31.7 Mbps │ ● connected │
171
+ │ 3 │ 🇹🇷 Istanbul - Bosphorus │ tcp:443 │ 61 ms │ 12.4 Mbps │ ✓ 2m ago │
172
+ │ 4 │ 🇫🇷 Paris - Seine │ udp │ 120 ms │ 22.1 Mbps │ ✗ timeout · 5m ago │
173
+ ╰─────┴─────────────────────────────┴─────────────┴─────────┴───────────┴────────────────────╯
174
+ [ ▶ Connect ] [ Disconnect ] [ Speed test all ] [ Latency test all ] [ Watch: on ]
175
+ [ New scan ] [ Quit ]
176
+ ```
177
+
178
+ | Key | Action |
179
+ |---|---|
180
+ | `↑` `↓` / `j` `k`, PgUp/PgDn, Home/End | move |
181
+ | `enter` | connect to the selected one |
182
+ | `d` | disconnect |
183
+ | `s` | speed test them all again; it first says how long that takes and how much it downloads (up to 10 MB each), and `s` again starts it |
184
+ | `l` | latency test them all again (no downloads); `l` again starts it |
185
+ | `o` | sort by speed or by latency |
186
+ | `w` | watch on or off |
187
+ | `b` | background checks on or off |
188
+ | `n` | new scan: back to the picker, with your saved settings |
189
+ | `esc` | stop what's running (a test, or connecting); with nothing running, quit |
190
+ | `q` | quit; the VPN stays as it is (press again to quit right away) |
191
+
192
+ **Mouse:** click a row to select it, double-click it to connect, scroll with the wheel. The buttons do the same as their keys.
193
+
194
+ **Where you're connected** is the box on top: a green `● CONNECTED` with the location, protocol, how long it's been up, its latency (checked every 3 s, with the last round trips as a sparkline), the VPN's IP, and how many times WindScout connected you again. While connecting, it shows each step and the time against the timeout. `▲ CONNECTION LOST` shows while it connects you again, `✗ NONE CONNECT NOW` when none of them does, and `● CONNECTED IN THE APP` when you connected in the Windscribe app yourself (WindScout doesn't watch that one). The terminal window's title says it too, for when the terminal is behind another window. The box also shows the last two things that happened, with the time, on a tall enough terminal.
195
+
196
+ **Connecting:** when the one you chose doesn't connect, WindScout goes back to the one you were on, if any, else it tries the next ones in the list, around to the one before it. The one that didn't connect goes to the end of the list, marked ✗ with why and when.
197
+
198
+ **Testing them all again** (`s` or `l`) goes through the list one at a time on this computer, as a scan does: it connects, measures, disconnects. Your VPN switches as it goes. Afterwards the list is ranked by what the test found, and WindScout connects you back to the one you had if it still works, else to the best. When you stop the test (`esc`), it connects you back too. The ones that didn't connect go to the end, marked ✗: they may work again later, and the next test tries them again. When none connects, that's your network rather than them, so the list stays as it was.
199
+
200
+ **Watching** (on by default, remembered) keeps you connected. Every 3 s it checks the connection: still connected, with internet through it. After 3 misses in a row, it connects again. If the connection had lasted 5 minutes, it tries the same one first; one that dropped sooner is likely blocked, so it goes to the end of the list and the next best is tried. When none connects, it waits 30 s and tries them all again from the best. Disconnecting or switching in the Windscribe app is yours to do: WindScout tells that from a drop by the app's own log (`~/.local/share/Windscribe/Windscribe2/client.log` on Linux) and leaves the VPN alone. Where it can't read that log, it takes your disconnect for a drop, so press `d` in WindScout instead. With watching off, a drop just shows (`▲ NO INTERNET`).
201
+
202
+ **Background checks** (`b`, remembered; on by default once you've used [parallel scans](#parallel-scan-with-docker)) keep the list fresh while you stay connected. While watching, every 5 minutes, 3 docker containers connect to each connection in the list and measure its latency. Your own VPN connection isn't touched, and nothing pops up: only the line in the box says what they're doing. They skip the one you're on, since the watch checks it already, and they don't run a speed test, since that would cost data. What works gets its new latency, and what doesn't goes to the end of the list. A check where nothing worked changes nothing. The first check comes 5 minutes after the scan, or right away for an older saved list. The containers stay up between checks, at about 120 MB each, and go away when you turn the checks off, quit, or start a new scan. If docker isn't set up, `b` offers to install or start it, as `d` in the picker does.
203
+
204
+ The containers have to go out on your own network, not through your VPN: through it, they'd test from the VPN's server and everything would look like it works.
205
+
206
+ - **Docker on Linux:** WindScout puts them on a docker network of their own, `windscout-direct`. A routing rule then sends that network's traffic out your normal gateway: routing table 2470, at rule priorities 89-90, set up by a short-lived container on the host network. Only that network's traffic is affected. Windscribe's firewall filters this computer's own traffic, not what it forwards, so it doesn't block them. The rule goes away when the checks stop.
207
+ - **Docker Desktop** (macOS, Windows) sends its containers' traffic through your VPN, and WindScout can't route around that.
208
+
209
+ Either way, before each check WindScout compares the containers' outside IP with your VPN's. If they're the same, it turns the checks off and says why. With Docker Desktop, excluding docker in Windscribe's split tunneling lets the checks run.
210
+
211
+ **Quitting** leaves the VPN as it is. If you quit while connecting to one you chose, that attempt finishes first. If you quit during a test, it connects you back to the one you had first. Press `q` again to quit right away. On the way out, WindScout prints the list as it is now (when it changed) and whether you're connected.
212
+
213
+ **New scan** (`n`): WindScout stops what's running and the background checks, then opens the picker with your saved settings. When you're connected, it asks first, since the scan switches your VPN (it disconnects it before it starts).
214
+
215
+ The list is [saved](#last-result-rescan-and-connect) as it changes, so `windscout connect` and the scanning types by the last result use what the screen found.
216
+
146
217
  ## Last result: rescan and connect
147
218
 
148
- After each scan, WindScout saves its working connections, best first, to `~/.config/windscout/last.json`. That includes a partial scan you stopped, and a scan started with `-c`. A scan's working connections replace the saved ones it tried, so the ones that stopped working drop out. The saved connections it didn't try stay, after its own: those of countries it didn't scan, and what a stopped scan didn't get to. A scan where nothing worked keeps the previous result as it is, since the network may just be down.
219
+ After each scan, WindScout saves its working connections, best first, to `~/.config/windscout/last.json`, and the [results screen](#results-screen-connect-test-again-stay-connected) updates them as it learns more. The ones whose last check didn't connect stay, at the end, marked with why: they may work again. Versions before 0.9.0 read those as working ones, and try them last. That includes a partial scan you stopped, and a scan started with `-c`. A scan's working connections replace the saved ones it tried, so the ones that stopped working drop out. The saved connections it didn't try stay, after its own: those of countries it didn't scan, and what a stopped scan didn't get to. A scan where nothing worked keeps the previous result as it is, since the network may just be down.
149
220
 
150
221
  - **Rescan:** the [scanning types](#scanning-type) *by last success result first* and *only the last results* use the saved connections: the first tries the ones in your selection before the rest of it, the second tries only them.
151
- - **Connect:** `windscout connect` connects to the best saved connection, without a scan. It checks each as the scan does: when one doesn't connect within the timeout (`-t`, or the saved one), or has no internet through it, it tries the next, until one connects. When none does, or there's no saved scan, it says so and suggests a rescan or a new scan.
222
+ - **Connect:** `windscout connect` connects to the best saved connection, without a scan. It checks each as the scan does: when one doesn't connect within the timeout (`-t`, or the saved one), or has no internet through it, it tries the next, until one connects. In a terminal, that happens on the [results screen](#results-screen-connect-test-again-stay-connected), which then keeps you connected (`n` there starts a scan). Without a terminal, it connects and exits. When none connects, or there's no saved scan, it says so and suggests a rescan or a new scan.
152
223
 
153
224
  ```sh
154
- windscout connect # the best saved connection that still works
225
+ windscout connect # the best saved connection that still works, on the results screen
155
226
  windscout connect -t 30 # wait longer for each
156
227
  ```
157
228
 
@@ -164,20 +235,22 @@ Press `o` in the picker, or pass `-o`, to choose what a scan tries and in which
164
235
  | default | `default` | the selected countries × protocols, the countries in list order |
165
236
  | by random country | `random` | the same, with the countries shuffled, a new order each run |
166
237
  | by last success result first | `last-first` | the same, but the [last result](#last-result-rescan-and-connect)'s working connections among them go first, best first and on their own port, then the rest. With one country selected, only that country is scanned, its saved connections first. The line shows how many saved connections are in your selection (`1 of 7 saved selected · 3h ago`) |
167
- | only the last results | `last-only` | only the last result's working connections, each on its own protocol and port. The countries, protocols and all ports are skipped, so the picker greys them out and locks them (your selection stays for next time). With `-o last-only`, `-c` isn't needed |
238
+ | only the last results | `last-only` | only the last result's working connections, each on its own protocol and port. The countries, protocols and all ports are skipped, so the picker greys them out and locks them. Its country list shows just the countries this scan tries, all selected; your own selection comes back when you change the type, and stays for next time. With `-o last-only`, `-c` isn't needed |
168
239
 
169
240
  The two types by the last result need a saved scan. Until there is one, choosing either turns `[ ▶ Start ]` off (greyed out), and a warning takes the estimate's place: `no saved scan yet, so start is off: press o for another scanning type`. Without the picker, `-o last-first` and `-o last-only` stop with an error instead. The estimate counts each saved connection once: `tcp` and `tcp:443` are the same attempt when 443 is tcp's default port.
170
241
 
171
242
  ### Stop at the first working connection
172
243
 
173
- Press `w` in the picker, or pass `-w`, to end the scan at the first connection that works and holds up: the attempts still connecting give up, and no more start. The scan stops at one connection, so that one has to be good for long use. A tunnel can connect and then be cut or throttled once traffic flows, so each connection that connects is checked first:
244
+ Press `w` in the picker, or pass `-w`, to end the scan at the first connection that works and holds up: the attempts still connecting give up, and no more start. The scan stops at one connection, so that one has to be good for long use. A tunnel can connect and then be cut or throttled once traffic flows, or refuse the next connection, which is the one you make. So each connection that connects is checked first:
174
245
 
175
- 1. A speed test, even with the speed test off (`s`, `-n`). It must reach at least 1 Mbps.
176
- 2. Then it has to stay connected, with internet through it, for 5 round trips 2 s apart (about 10 s), each round trip allowed one retry.
246
+ 1. **Speed test**, even with the speed test off (`s`, `-n`). It must reach at least 1 Mbps.
247
+ 2. **Hold:** it has to stay connected to the same server for 30 s, with a round trip through it every 2 s. A lost round trip now and then is fine, but 2 in a row (an outage), or more than 1 in 10, fail it. Change the 30 s with `h` in the picker or `-H` (at least 10, saved with your other settings): a longer hold catches connections that drop later, but takes longer for each one.
248
+ 3. **Speed test again.** It must still reach 1 Mbps, which catches a connection that gets throttled once traffic flows. The table shows the slower of the two.
249
+ 4. **Reconnect:** it disconnects and connects again the way you will, by the datacenter's nickname (`windscribe-cli connect "Castle" stealth`): within the timeout, with internet through it.
177
250
 
178
- A connection that drops during the check gets the status `unreliable` and doesn't count as working: it isn't saved, or offered to connect to. One that stays up but is slower than 1 Mbps still counts as working, but the scan doesn't stop there and keeps looking. If nothing passes, the scan runs to the end, and the report shows whatever worked.
251
+ A connection that fails a check gets the status `unreliable` and doesn't count as working: it isn't saved, or offered to connect to. One slower than 1 Mbps still counts as working, but isn't checked: the scan never stops there, and keeps looking. If nothing passes, the scan runs to the end, the report shows whatever worked, and a line above it says those connections weren't checked.
179
252
 
180
- The check adds up to about 30 s to each connection that connects (the speed test alone takes up to 15 s). The estimate reads `up to N attempts · at most ≈ …`, for when none holds up. With a type by the last result, the best saved connection goes first, and the line shows about how long the scan takes if it still holds up (`≈ 33s if DE stealth still holds up`). It's the quickest way back online: *only the last results* or *by last success result first*, with `w`.
253
+ The checks add about 1m 15s to each connection that passes the first speed test (each speed test takes up to 15 s), more with a longer hold; the `w` line shows it. The estimate reads `up to N attempts · ≈ … if none connects`. With a type by the last result, the best saved connection goes first, and the `w` line shows about how long the scan takes if it still holds up (`≈ 1m 15s if DE stealth still holds up`). It's the quickest way back online: *only the last results* or *by last success result first*, with `w`. In a [parallel scan](#parallel-scan-with-docker), the containers take turns for the speed tests and the hold, as they do for measuring, and each reconnects after its turn.
181
254
 
182
255
  ## Connect-only mode
183
256
 
@@ -198,7 +271,7 @@ Both modes show an **availability grid**: one row per country, and ✓ / ✗ for
198
271
  ╰─────────────────────────┴────────────────────────┴─────────┴───────────┴─────╯
199
272
  ```
200
273
 
201
- 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.
274
+ 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. The scan starts on the ranking (*Working so far*) until you switch: then the next scans start on the table you left it on (saved to `~/.config/windscout/view.json`).
202
275
 
203
276
  ## All ports
204
277
 
@@ -248,7 +321,7 @@ Then the first parallel scan builds a local image, `windscout-worker`, with Node
248
321
 
249
322
  The mirrors come from [Mirava](https://github.com/MiravaOrg/Mirava)'s list of mirrors in Iran; they come and go, and that's where to look for new ones.
250
323
 
251
- **Kernel check:** each container first checks that docker's Linux kernel gives it what Windscribe needs: a tunnel device (tun) and a firewall (nftables). If not, the scan stops and says so, rather than showing every location as blocked. Windscribe's WireGuard also needs nftables' `fib` in its firewall table, which Docker Desktop's own Linux VM lacks (on macOS, and on Windows without WSL 2). There, WireGuard runs on this computer instead (see below).
324
+ **Kernel check:** each container first checks that docker's Linux kernel gives it what Windscribe needs: a tunnel device (tun) and a firewall (nftables). If not, the scan stops and says so, rather than showing every location as blocked. Windscribe's WireGuard also needs nftables' `fib` in its firewall table, which Docker Desktop's own Linux VM lacks (on macOS, and on Windows without WSL 2). There, a parallel scan skips WireGuard (see below).
252
325
 
253
326
  **Login:** the containers' Windscribe apps log in with the session that the Windscribe app on this computer is logged in with, so they never log in with your password. Windscribe locks an account that logs in many times at once, and a login per container would do that. WindScout reads the session from the app's settings (`~/.config/Windscribe/Windscribe2.conf` on Linux, the `com.windscribe.Windscribe2` defaults on macOS, `HKEY_CURRENT_USER\Software\Windscribe\Windscribe2` in the registry on Windows) and hands it to each container on its standard input, not on a command line, where other programs could see it. Each container's docker volume (`windscout-1`, `windscout-2`, …) keeps its app's settings for the next scans, with the WireGuard keys it registered with your account. A volume that another account used starts over. If you log out of the Windscribe app, the containers' session ends too, and the next scan gives them the new one.
254
327
 
@@ -260,7 +333,7 @@ WindScout 0.6.0 asked for your username and password instead, and could save the
260
333
 
261
334
  **Differences from a normal scan:**
262
335
 
263
- - IKEv2, and WireGuard when the kernel check finds it can't run in the containers, are tried on this computer once the containers are done, one at a time like a normal scan. The scan screen shows them on a line of their own (`this computer`), and the picker's estimate counts them. Windscribe's Linux app has no IKEv2.
336
+ - The containers do the whole scan: nothing runs on this computer. What they can't run is skipped: IKEv2 (Windscribe's Linux app has none), and WireGuard when the kernel check finds it can't run in the containers. The picker's docker line says so (`ikev2 skipped`), its estimate leaves them out, and a line after the scan lists them. To try them, scan without docker. If every container stops, the scan ends there, with what they found, and says why.
264
337
  - This computer's own Windscribe connection is disconnected at the start and stays off while the containers scan, since it would carry their traffic too.
265
338
  - No firewall: "block internet while scanning" isn't used, since it would block the containers' traffic too.
266
339
 
@@ -310,7 +383,7 @@ The source is TypeScript in `src/`. It compiles to `dist/`, which is what gets p
310
383
  ```sh
311
384
  npm install
312
385
  npm run build # compile src/ to dist/ (the windscout command is dist/cli.js)
313
- npm test # build, then check the scan's scheduling (no VPN, no docker)
386
+ npm test # build, then check the scan, the results screen's keeper and the status parsing (no VPN, no docker)
314
387
  node dist/cli.js # run your build; takes the same options as windscout
315
388
  ./publish.sh # build, install the packed tarball to check it runs, publish to npm (bump the version first)
316
389
  ```
@@ -0,0 +1,131 @@
1
+ // Background checks, for the results screen's watch. Every few minutes, docker containers connect to each connection in the
2
+ // list and measure its latency, so the list stays fresh while you stay connected: this computer's VPN isn't touched. There's no
3
+ // speed test, since that would cost data. The containers must go out on their own, not through your VPN: through it, they'd
4
+ // test from its server instead of your network, and everything would look like it works. On Linux, windscout routes its
5
+ // containers around the VPN (docker.ts). And before each check, it compares the IP the containers go out from with the VPN's,
6
+ // and checks only when they differ.
7
+ import { buildImage, DIRECT_NETWORK, directNetwork, dockerState, nativeDocker, notInContainers, routeAround, startWorkers, unroute } from "./docker.js";
8
+ import { outsideIp } from "./measure.js";
9
+ import { target } from "./probe.js";
10
+ import { Scan } from "./scan.js";
11
+ import { connectState, session } from "./windscribe.js";
12
+ // ponytail: checks a list of ~10 in about a minute, with far less load than a scan; raise if lists grow. They use the first
13
+ // containers' volumes, as a parallel scan does: the WireGuard keys in there are registered with the account already
14
+ const CONTAINERS = 3;
15
+ /** Checks in docker containers, which stay up between the checks until stop(); timeout: seconds to wait for each connection.
16
+ * One thing at a time: a check, or a stop, waits for the one before it to end (a stop makes it end within seconds) */
17
+ export function dockerChecker(timeout) {
18
+ let workers = null, subnet = "", off = "";
19
+ let abort = new AbortController(); // stop() ends what's running
20
+ let queue = Promise.resolve();
21
+ const later = (fn) => {
22
+ const p = queue.then(fn);
23
+ queue = p.catch(() => { });
24
+ return p;
25
+ };
26
+ const say = (text) => (checker.status = text);
27
+ /** Gets the containers up, unless they are; false when it can't, or was stopped */
28
+ const ready = async (list, stopped, signal) => {
29
+ const over = () => stopped() || signal.aborted;
30
+ if (off)
31
+ return say(off), false;
32
+ if (workers?.alive())
33
+ return true;
34
+ say("checking docker");
35
+ if ((await dockerState()) !== "ready")
36
+ return say("docker isn't running: they try again next time"), false;
37
+ const s = session();
38
+ if (!s)
39
+ return say("can't find this computer's Windscribe session for the containers: update the Windscribe app"), false;
40
+ say("getting the containers' image ready (the first time takes a few minutes)");
41
+ await buildImage(() => { }, signal);
42
+ if (over())
43
+ return false;
44
+ if (nativeDocker())
45
+ subnet ||= await directNetwork(); // routed around your VPN before each check
46
+ if (over())
47
+ return false;
48
+ const n = Math.max(1, Math.min(CONTAINERS, list.length));
49
+ say(`starting ${n} container${n > 1 ? "s" : ""} (about a minute)`);
50
+ const w = await startWorkers(n, s, () => { }, { network: subnet ? DIRECT_NETWORK : undefined, tag: "check-", signal });
51
+ if (over())
52
+ return await w.stop(), false;
53
+ if (!w.probers.length)
54
+ return say("no container got ready: they try again next time"), false;
55
+ workers = w;
56
+ say("");
57
+ return true;
58
+ };
59
+ /** Whether the containers go out their own way: compared with your VPN's IP while you're connected */
60
+ const direct = async (w) => {
61
+ const vpn = (await connectState()).startsWith("Connected") ? await outsideIp() : "";
62
+ if (!vpn || vpn !== (await w.whoami()))
63
+ return true;
64
+ off = nativeDocker() ? "off: their traffic goes through your VPN, and routing it around didn't work"
65
+ : "off: Docker Desktop sends their traffic through your VPN (excluding docker in Windscribe's split tunneling lets them check)";
66
+ say(off);
67
+ return false;
68
+ };
69
+ const check = async (list, stopped, signal) => {
70
+ const over = () => stopped() || signal.aborted;
71
+ if (!(await ready(list, stopped, signal)) || over())
72
+ return null;
73
+ const w = workers;
74
+ if (subnet)
75
+ await routeAround(subnet); // again each time: you may be on another network now
76
+ if (!(await direct(w)))
77
+ return (workers = null), await w.stop(), null;
78
+ if (over())
79
+ return null;
80
+ // what the containers can't run (IKEv2, WireGuard on Docker Desktop's own kernel) stays as it was
81
+ const skip = notInContainers(list.map((e) => e.protocol), w.wireguard);
82
+ const only = list.filter((e) => !skip.has(e.protocol)).map((e) => ({ country: target(e), protocol: e.protocol }));
83
+ if (!only.length)
84
+ return say("the containers can't run these protocols"), null;
85
+ const scan = new Scan([], [], { timeout, speedTest: false, only }, w.probers);
86
+ say(`checking ${only.length} in ${w.probers.length} container${w.probers.length > 1 ? "s" : ""}`);
87
+ const tick = setInterval(() => over() && scan.stop(), 500);
88
+ try {
89
+ await scan.run({ result: () => say(`checking: ${scan.done} of ${scan.total}`) });
90
+ }
91
+ finally {
92
+ clearInterval(tick);
93
+ }
94
+ if (!w.alive())
95
+ workers = null; // they stopped: started again next time
96
+ if (over())
97
+ return null;
98
+ if (!scan.results.length)
99
+ return say(scan.errors[0] ?? "nothing got checked"), null;
100
+ say("");
101
+ return scan.results;
102
+ };
103
+ const guarded = (fn) => later(async () => {
104
+ try {
105
+ return await fn(abort.signal);
106
+ }
107
+ catch (e) {
108
+ if (!abort.signal.aborted)
109
+ say(`couldn't check: ${e.message}`);
110
+ return null;
111
+ }
112
+ });
113
+ const checker = {
114
+ status: "",
115
+ prepare: (list, stopped) => guarded(async (signal) => (await ready(list, stopped, signal)) || null).then(Boolean),
116
+ check: (list, stopped) => guarded((signal) => check(list, stopped, signal)),
117
+ stop() {
118
+ abort.abort();
119
+ return later(async () => {
120
+ const w = workers;
121
+ workers = null;
122
+ await w?.stop();
123
+ await unroute();
124
+ say("");
125
+ off = ""; // turned on again: they try again
126
+ abort = new AbortController();
127
+ });
128
+ },
129
+ };
130
+ return checker;
131
+ }