windscout 0.6.5 → 0.7.1
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 +96 -7
- package/dist/cli.js +122 -43
- package/dist/config.js +47 -2
- package/dist/docker.js +5 -5
- package/dist/measure.js +1 -1
- package/dist/probe.js +83 -45
- package/dist/scan.js +43 -22
- package/dist/tui/docker.js +2 -2
- package/dist/tui/picker.js +134 -65
- package/dist/tui/progress.js +20 -12
- package/dist/tui/tables.js +1 -1
- package/dist/tui/terminal.js +21 -0
- package/dist/tui/widgets.js +2 -0
- package/dist/worker.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,57 @@ npm install -g windscout # or run once with: npx windscout
|
|
|
23
23
|
|
|
24
24
|
On startup, WindScout checks npm for a newer version. Above the repository link at the bottom of the screen, it shows the installed version in grey (`windscout 0.5.6 · latest` once npm confirms nothing newer is out), or, when a newer one is out, an update notice in yellow. In the picker, press `u` to update and restart on the new version. The update runs `npm install -g windscout@latest`, with `sudo` when your global npm folder belongs to root (sudo then asks for your password). The update command is also printed after the final report.
|
|
25
25
|
|
|
26
|
+
### In a sandbox (Docker)
|
|
27
|
+
|
|
28
|
+
To run WindScout without installing it or Node on your computer, run it in a container. The container has its own Windscribe app (Windscribe's Linux CLI app) and, for [parallel scans](#parallel-scan-with-docker), its own docker daemon inside it (Docker-in-Docker). You need Docker: on Linux, docker itself; on macOS and Windows, [Docker Desktop](https://docs.docker.com/desktop/) (on Windows with its WSL 2 engine, set to Linux containers). Build the image once, the same way on every system. Docker fetches this repository from GitHub itself, so there's nothing to clone (add `#` and a branch, tag or commit to the URL to build that one, as in `windscout.git#main`; in a clone, use `.` in place of the URL to build your copy):
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
docker build -t windscout-sandbox -f sandbox/Dockerfile https://github.com/Diercohen/windscout.git
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then run it with the command for your system, below. If the Windscribe app on your computer is logged in, the container's app logs in with that app's session, as the [parallel scan's containers](#parallel-scan-with-docker) do: the command hands it to the container in a file mounted read-only, and the container copies only the session (`userId` and `authHash`) into its own app's settings. Without the app, or when its session doesn't work, the container asks for your Windscribe username and password (and 2FA code, if you use one), and the `windscout-home` volume keeps that login for the next runs. Then the picker opens as usual. Options after the image name go to WindScout: `windscout-sandbox -c "DE,NL" -d`. The `windscout-docker` volume keeps the inner docker's `windscout-worker` image and its containers' volumes, so only the first parallel scan builds them.
|
|
35
|
+
|
|
36
|
+
**Linux:** the app's settings file is mounted as it is (read-only, never changed). Without the Windscribe app, leave out the `Windscribe2.conf` line: otherwise docker creates an empty folder in its place.
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
docker run -it --rm --privileged \
|
|
40
|
+
-v ~/.config/Windscribe/Windscribe2.conf:/host/Windscribe2.conf:ro \
|
|
41
|
+
-v windscout-home:/home/ws -v windscout-docker:/var/lib/docker \
|
|
42
|
+
windscout-sandbox
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**macOS** (Terminal): the app keeps its session in its defaults, so the first line writes it to a private temporary file, which the last line deletes after the run.
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
s=$(mktemp) && printf '[General]\nuserId=%s\nauthHash=%s\n' "$(defaults read com.windscribe.Windscribe2 userId)" "$(defaults read com.windscribe.Windscribe2 authHash)" > "$s"
|
|
49
|
+
docker run -it --rm --privileged \
|
|
50
|
+
-v "$s:/host/Windscribe2.conf:ro" \
|
|
51
|
+
-v windscout-home:/home/ws -v windscout-docker:/var/lib/docker \
|
|
52
|
+
windscout-sandbox
|
|
53
|
+
rm -f "$s"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Windows** (PowerShell): the app keeps its session in the registry, so the first lines write it to a private temporary file, which the last line deletes after the run.
|
|
57
|
+
|
|
58
|
+
```powershell
|
|
59
|
+
$k = Get-ItemProperty HKCU:\Software\Windscribe\Windscribe2
|
|
60
|
+
$s = New-TemporaryFile
|
|
61
|
+
Set-Content $s "[General]`nuserId=$($k.userId)`nauthHash=$($k.authHash)"
|
|
62
|
+
docker run -it --rm --privileged `
|
|
63
|
+
-v "$($s.FullName):/host/Windscribe2.conf:ro" `
|
|
64
|
+
-v windscout-home:/home/ws -v windscout-docker:/var/lib/docker `
|
|
65
|
+
windscout-sandbox
|
|
66
|
+
Remove-Item $s
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Without the Windscribe app on macOS or Windows, run only the `docker run` command, without its `Windscribe2.conf` line.
|
|
70
|
+
|
|
71
|
+
- `--privileged` is needed: the inner docker, and the Windscribe app's tunnel and firewall, need it. It gives the container broad control of docker's Linux kernel (on Linux, your computer's), so this isolates files and installs, not security. VPN connections stay inside the container: your computer's own connection doesn't change.
|
|
72
|
+
- Docker Desktop's own Linux VM (macOS, Windows without WSL 2) can't run Windscribe's WireGuard (see the kernel check under [parallel scans](#parallel-scan-with-docker)), and in the sandbox that goes for its own app too. Windscribe's Linux app has no IKEv2, so the sandbox doesn't try it.
|
|
73
|
+
- Where Docker Hub refuses (Iran), take the base image from a mirror: `docker build --build-arg BASE=registry.docker.ir/library/node:22-bookworm-slim -t windscout-sandbox -f sandbox/Dockerfile https://github.com/Diercohen/windscout.git`
|
|
74
|
+
- Updating from inside (`u`) doesn't last, since each run starts fresh from the image: run the build command again instead, for the latest WindScout; add `--no-cache` to get the latest Windscribe app too.
|
|
75
|
+
- To remove everything: `docker rmi windscout-sandbox` and `docker volume rm windscout-home windscout-docker`.
|
|
76
|
+
|
|
26
77
|
## Usage
|
|
27
78
|
|
|
28
79
|
Run `windscout` with no arguments to open the interactive picker:
|
|
@@ -54,17 +105,18 @@ Run `windscout` with no arguments to open the interactive picker:
|
|
|
54
105
|
| `p` | toggle [all ports](#all-ports): each protocol on every port it has, not just its default |
|
|
55
106
|
| `s` | toggle the speed test (off = connect-only mode) |
|
|
56
107
|
| `f` | toggle "block internet while scanning" (firewall) |
|
|
57
|
-
| `r` | toggle random order: scan the selected countries shuffled, a new order each run |
|
|
58
108
|
| `d` | toggle the [parallel scan in docker containers](#parallel-scan-with-docker) (installs or starts docker first, after asking) |
|
|
109
|
+
| `o` | change the [scanning type](#scanning-type): default, by random country, by last success result first, only the last results |
|
|
110
|
+
| `w` | toggle [stop at the first working connection](#stop-at-the-first-working-connection) that holds up |
|
|
59
111
|
| `u` | update to the newer version and restart (only when the picker shows one is out) |
|
|
60
112
|
| `enter` | start scanning |
|
|
61
113
|
| `q` | quit (during a scan: stop and show partial results; press again to force quit) |
|
|
62
114
|
|
|
63
|
-
**Mouse:** click a country to select or unselect it, click a protocol or
|
|
115
|
+
**Mouse:** click a country to select or unselect it, click a protocol or a setting's line to toggle it (the scanning type's line changes the type), click the search box to start typing (click anywhere else to leave it), and scroll the list with the wheel. The `[ ▶ Start ] [ All ] [ None ] [ Quit ]` buttons under the table do the same as their keys; the shortcut key is underlined on screen (on the buttons, the protocol numbers and `s`). This needs a terminal with mouse support, which most have. On Windows, use Windows Terminal; if clicks do nothing there, the keyboard does everything.
|
|
64
116
|
|
|
65
117
|
Your selection is saved to `~/.config/windscout/config.json` (`%USERPROFILE%\.config\windscout\config.json` on Windows) and preselected next time.
|
|
66
118
|
|
|
67
|
-
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.
|
|
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 or `0` to connect to the best one, type another one's number and press enter to connect to that, or press `q` to quit. `0` and `q` work right away, without enter.
|
|
68
120
|
|
|
69
121
|
### Non-interactive
|
|
70
122
|
|
|
@@ -86,10 +138,47 @@ windscout -t 40 -a # longer connect timeout, also list
|
|
|
86
138
|
| `-a, --all` | include failed attempts in the final table | off |
|
|
87
139
|
| `-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 |
|
|
88
140
|
| `-f, --firewall` | block internet while scanning, except through the VPN | picker: saved, or off; with `-c`: off |
|
|
89
|
-
| `-
|
|
141
|
+
| `-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
|
+
| `-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 |
|
|
90
143
|
| `-d, --docker` | [scan in parallel, in docker containers](#parallel-scan-with-docker) | picker: saved, or off; with `-c`: off |
|
|
91
144
|
| `-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 | |
|
|
92
145
|
|
|
146
|
+
## Last result: rescan and connect
|
|
147
|
+
|
|
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.
|
|
149
|
+
|
|
150
|
+
- **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.
|
|
152
|
+
|
|
153
|
+
```sh
|
|
154
|
+
windscout connect # the best saved connection that still works
|
|
155
|
+
windscout connect -t 30 # wait longer for each
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Scanning type
|
|
159
|
+
|
|
160
|
+
Press `o` in the picker, or pass `-o`, to choose what a scan tries and in which order. The picker's line shows the type, and the estimate under the table counts what that type scans. The type is remembered with your other settings.
|
|
161
|
+
|
|
162
|
+
| Type | `-o` | What it scans |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| default | `default` | the selected countries × protocols, the countries in list order |
|
|
165
|
+
| by random country | `random` | the same, with the countries shuffled, a new order each run |
|
|
166
|
+
| 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. 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
|
+
|
|
169
|
+
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
|
+
|
|
171
|
+
### Stop at the first working connection
|
|
172
|
+
|
|
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:
|
|
174
|
+
|
|
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.
|
|
177
|
+
|
|
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.
|
|
179
|
+
|
|
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`.
|
|
181
|
+
|
|
93
182
|
## Connect-only mode
|
|
94
183
|
|
|
95
184
|
Often you only need to know **what connects**, not how fast it is. Turn the speed test off with `s` in the picker or with `--no-speed`. Each working connection is then checked with a quick latency test only. That saves about 15 s per working connection, and results are ranked by latency. Connect-only is the default on the first run, and the choice is remembered.
|
|
@@ -109,7 +198,7 @@ Both modes show an **availability grid**: one row per country, and ✓ / ✗ for
|
|
|
109
198
|
╰─────────────────────────┴────────────────────────┴─────────┴───────────┴─────╯
|
|
110
199
|
```
|
|
111
200
|
|
|
112
|
-
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.
|
|
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. 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`).
|
|
113
202
|
|
|
114
203
|
## All ports
|
|
115
204
|
|
|
@@ -159,7 +248,7 @@ Then the first parallel scan builds a local image, `windscout-worker`, with Node
|
|
|
159
248
|
|
|
160
249
|
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.
|
|
161
250
|
|
|
162
|
-
**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,
|
|
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, a parallel scan skips WireGuard (see below).
|
|
163
252
|
|
|
164
253
|
**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.
|
|
165
254
|
|
|
@@ -171,7 +260,7 @@ WindScout 0.6.0 asked for your username and password instead, and could save the
|
|
|
171
260
|
|
|
172
261
|
**Differences from a normal scan:**
|
|
173
262
|
|
|
174
|
-
- IKEv2, and WireGuard when the kernel check finds it can't run in the containers
|
|
263
|
+
- 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.
|
|
175
264
|
- 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.
|
|
176
265
|
- No firewall: "block internet while scanning" isn't used, since it would block the containers' traffic too.
|
|
177
266
|
|
package/dist/cli.js
CHANGED
|
@@ -5,24 +5,27 @@
|
|
|
5
5
|
import { createInterface } from "node:readline";
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
7
|
import { parseArgs } from "node:util";
|
|
8
|
-
import { CONFIG, DEFAULT_TIMEOUT, forgetLogin, loadConfig, loadDockerWireGuard, MIN_TIMEOUT, saveDockerWireGuard, SPEED_TEST } from "./config.js";
|
|
8
|
+
import { CONFIG, DEFAULT_TIMEOUT, forgetLogin, LAST_FILE, loadConfig, loadDockerWireGuard, loadLast, MIN_TIMEOUT, saveDockerWireGuard, saveLast, SPEED_TEST } from "./config.js";
|
|
9
9
|
import { notInContainers, stopWorkers } from "./docker.js";
|
|
10
|
-
import { probe } from "./probe.js";
|
|
11
|
-
import { Scan } from "./scan.js";
|
|
10
|
+
import { attempt, disconnect, MIN_MBPS, probe } from "./probe.js";
|
|
11
|
+
import { ORDERS, plan, Scan } from "./scan.js";
|
|
12
12
|
import { startParallel } from "./tui/docker.js";
|
|
13
13
|
import { pick } from "./tui/picker.js";
|
|
14
14
|
import { logProgress, showProgress } from "./tui/progress.js";
|
|
15
15
|
import { command, report, target } from "./tui/report.js";
|
|
16
16
|
import { leaveScreen, out } from "./tui/terminal.js";
|
|
17
17
|
import { cyan, green, yellow } from "./tui/text.js";
|
|
18
|
-
import {
|
|
18
|
+
import { duration, now } from "./time.js";
|
|
19
19
|
import { canUpdate, checkUpdate, runUpdate, updateNotice, versionLines } from "./update.js";
|
|
20
|
-
import { blockInternet, CLI,
|
|
20
|
+
import { blockInternet, CLI, isFirewallOn, protocolPorts, PROTOCOLS, restoreFirewall, waitForLogin, windscribe, withPorts } from "./windscribe.js";
|
|
21
21
|
import { worker } from "./worker.js";
|
|
22
22
|
const HELP = `Usage: windscout [options]
|
|
23
|
+
windscout connect [-t SEC]
|
|
23
24
|
|
|
24
25
|
Find which Windscribe locations and protocols work from your network, ranked by ${SPEED_TEST ? "speed" : "latency"}.
|
|
25
26
|
Without -c it opens an interactive country picker (your selection is remembered).
|
|
27
|
+
Each scan's working connections are saved: the scanning type (-o, or o in the picker) can try
|
|
28
|
+
them first or only them, and windscout connect connects to the best of them that still works.
|
|
26
29
|
|
|
27
30
|
Options:
|
|
28
31
|
-c, --countries LIST comma-separated ISO codes, city names or nicknames (skips the picker)
|
|
@@ -31,27 +34,48 @@ Options:
|
|
|
31
34
|
-t, --timeout SEC seconds to wait for each connection, at least ${MIN_TIMEOUT} (default: saved, or ${DEFAULT_TIMEOUT})
|
|
32
35
|
-a, --all also show failed attempts in the final table
|
|
33
36
|
${SPEED_TEST ? " -n, --no-speed connect-only: skip the download test, rank by latency (faster scan)\n" : ""} -f, --firewall block internet while scanning, except through the VPN (turned back off afterwards)
|
|
34
|
-
-
|
|
37
|
+
-o, --order TYPE scanning type: default (list order), random (countries shuffled), last-first (the last
|
|
38
|
+
result's working connections among them first, then the rest) or last-only (only those,
|
|
39
|
+
whatever -c and -p say; needs no -c)
|
|
40
|
+
-w, --stop-at-first stop at the first working connection that holds up: a speed test (even with -n) of at least
|
|
41
|
+
${MIN_MBPS} Mbps, then about 10 more seconds connected, with internet through it
|
|
35
42
|
-d, --docker scan in parallel, in docker containers (one per attempt, up to 16, as docker's CPUs and memory allow)
|
|
36
43
|
-v, --version show the installed version (and offer to update if a newer one is out)
|
|
37
44
|
-h, --help show this help
|
|
38
45
|
|
|
39
|
-
Selection is stored in ${CONFIG}
|
|
46
|
+
Selection is stored in ${CONFIG}, the last result in ${LAST_FILE}
|
|
40
47
|
Examples: windscout -c DE,NL,TR -p stealth,tcp:443
|
|
41
|
-
windscout -c DE -p stealth,tcp -P
|
|
48
|
+
windscout -c DE -p stealth,tcp -P
|
|
49
|
+
windscout connect`;
|
|
42
50
|
// "DE, NL,,TR" → ["DE", "NL", "TR"]
|
|
43
51
|
const list = (s) => s.split(",").map((x) => x.trim()).filter(Boolean);
|
|
44
|
-
|
|
52
|
+
/** The typed answer; one of instant is taken as soon as it's the whole answer, without enter */
|
|
53
|
+
const ask = (question, instant = []) => new Promise((resolve) => {
|
|
45
54
|
const rl = createInterface({ input: process.stdin, output: out });
|
|
55
|
+
// runs after readline's own keypress listener, so rl.line has the key in it
|
|
56
|
+
const onKey = () => instant.includes(rl.line.toLowerCase()) && (out.write("\n"), resolve(rl.line), rl.close());
|
|
57
|
+
process.stdin.on("keypress", onKey);
|
|
46
58
|
rl.on("SIGINT", () => rl.close()); // Ctrl-C / Ctrl-D: no answer, null
|
|
47
|
-
rl.on("close", () => resolve(null));
|
|
59
|
+
rl.on("close", () => (process.stdin.off("keypress", onKey), resolve(null)));
|
|
48
60
|
rl.question(question, (a) => (resolve(a.trim()), rl.close()));
|
|
49
61
|
});
|
|
50
|
-
/**
|
|
62
|
+
/** Runs a result's connect command and checks it as the scan does: connected, on its port, with internet through it.
|
|
63
|
+
* True once it is; otherwise disconnects (and waits until the app has), so the next connect isn't swallowed */
|
|
64
|
+
async function connectTo(r, timeout) {
|
|
65
|
+
console.log(cyan(command(r)));
|
|
66
|
+
const t = now(), { status, location } = await attempt(target(r), r.protocol, { timeout, speedTest: false });
|
|
67
|
+
if (status === "ok")
|
|
68
|
+
return console.log(green(`Connected: ${location}`)), true;
|
|
69
|
+
console.log(yellow(`${status} after ${duration(now() - t)}`));
|
|
70
|
+
await disconnect();
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
/** Asks which numbered connection to make (enter: the best), then runs its command and waits for it to connect.
|
|
74
|
+
* 0 and q need no enter; the other numbers do, since 1 may be the start of 10 */
|
|
51
75
|
async function offerConnect(choices, timeout) {
|
|
52
76
|
const others = choices.length > 2 ? `, 1-${choices.length - 1} = other` : choices.length > 1 ? ", 1 = other" : "";
|
|
53
77
|
for (;;) {
|
|
54
|
-
const a = await ask(`\nconnect to # (enter or 0 = best${others}, q = quit):
|
|
78
|
+
const a = await ask(`\nconnect to # (enter or 0 = best${others}, q = quit): `, ["0", "q"]);
|
|
55
79
|
if (a === null || /^q(uit)?$/i.test(a))
|
|
56
80
|
return;
|
|
57
81
|
const r = a === "" ? choices[0] : /^\d+$/.test(a) ? choices[Number(a)] : undefined;
|
|
@@ -59,14 +83,28 @@ async function offerConnect(choices, timeout) {
|
|
|
59
83
|
console.log(yellow(`no #${a} in the tables`));
|
|
60
84
|
continue;
|
|
61
85
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
86
|
+
await connectTo(r, timeout);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const RESCAN_HINT = "rescan them with windscout -o last-only, or start a new scan";
|
|
91
|
+
/** windscout connect: the last result's connections, best first, until one connects */
|
|
92
|
+
async function connectLast(timeout) {
|
|
93
|
+
const last = loadLast();
|
|
94
|
+
if (!last) {
|
|
95
|
+
console.log(yellow("no saved scan yet: run windscout to scan first"));
|
|
96
|
+
process.exitCode = 1;
|
|
68
97
|
return;
|
|
69
98
|
}
|
|
99
|
+
await waitForLogin();
|
|
100
|
+
await disconnect(); // a connection already up would read as the first one connecting
|
|
101
|
+
for (const [i, r] of last.results.entries()) {
|
|
102
|
+
console.log(`\n${i + 1}/${last.results.length} ${r.location || r.query} over ${r.protocol}`);
|
|
103
|
+
if (await connectTo(r, timeout))
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
console.log(yellow(`\nnone of the last result's ${last.results.length} connection${last.results.length === 1 ? "" : "s"} works now: ${RESCAN_HINT}`));
|
|
107
|
+
process.exitCode = 1;
|
|
70
108
|
}
|
|
71
109
|
/** u in the picker: update, then run the new version in this one's place, with the same arguments */
|
|
72
110
|
function updateAndRestart() {
|
|
@@ -80,7 +118,8 @@ function updateAndRestart() {
|
|
|
80
118
|
process.exitCode = spawnSync(process.execPath, [...process.execArgv, ...process.argv.slice(1)], { stdio: "inherit" }).status ?? 1;
|
|
81
119
|
}
|
|
82
120
|
async function main() {
|
|
83
|
-
const { values: a } = parseArgs({
|
|
121
|
+
const { values: a, positionals } = parseArgs({
|
|
122
|
+
allowPositionals: true,
|
|
84
123
|
options: {
|
|
85
124
|
countries: { type: "string", short: "c" },
|
|
86
125
|
protocols: { type: "string", short: "p" },
|
|
@@ -91,7 +130,9 @@ async function main() {
|
|
|
91
130
|
version: { type: "boolean", short: "v", default: false },
|
|
92
131
|
"no-speed": { type: "boolean", short: "n", default: false },
|
|
93
132
|
firewall: { type: "boolean", short: "f", default: false },
|
|
94
|
-
|
|
133
|
+
order: { type: "string", short: "o" },
|
|
134
|
+
"stop-at-first": { type: "boolean", short: "w", default: false },
|
|
135
|
+
random: { type: "boolean", short: "r", default: false }, // from before scanning types: -o random
|
|
95
136
|
docker: { type: "boolean", short: "d", default: false },
|
|
96
137
|
worker: { type: "boolean", default: false }, // in a parallel scan's container
|
|
97
138
|
},
|
|
@@ -103,6 +144,11 @@ async function main() {
|
|
|
103
144
|
forgetLogin();
|
|
104
145
|
if (a.help)
|
|
105
146
|
return console.log(HELP);
|
|
147
|
+
const [subcommand, ...extra] = positionals;
|
|
148
|
+
if (subcommand !== undefined && (subcommand !== "connect" || extra.length))
|
|
149
|
+
throw new Error(`unknown command '${positionals.join(" ")}': see --help`);
|
|
150
|
+
if (subcommand === "connect" && Object.entries(a).some(([k, v]) => k !== "timeout" && v))
|
|
151
|
+
throw new Error("windscout connect takes only -t: see --help");
|
|
106
152
|
checkUpdate(); // runs while the app starts and the user picks, before the VPN starts switching
|
|
107
153
|
if (a.version) {
|
|
108
154
|
await checkUpdate();
|
|
@@ -114,16 +160,23 @@ async function main() {
|
|
|
114
160
|
let timeout = a.timeout ? Number(a.timeout) : undefined;
|
|
115
161
|
if (timeout !== undefined && !(timeout >= MIN_TIMEOUT))
|
|
116
162
|
throw new Error(`--timeout must be at least ${MIN_TIMEOUT} seconds`);
|
|
163
|
+
if (a.order !== undefined && !ORDERS.includes(a.order))
|
|
164
|
+
throw new Error(`--order must be one of: ${ORDERS.join(", ")}`);
|
|
165
|
+
if (subcommand === "connect")
|
|
166
|
+
return connectLast(timeout ?? loadConfig()?.timeout ?? DEFAULT_TIMEOUT);
|
|
117
167
|
const status = await waitForLogin();
|
|
118
168
|
const tty = Boolean(process.stdin.isTTY && out.isTTY);
|
|
119
169
|
let countries = a.countries ? list(a.countries) : undefined;
|
|
120
170
|
let protocols = a.protocols ? list(a.protocols) : undefined;
|
|
121
171
|
let speedTest = SPEED_TEST && !a["no-speed"];
|
|
122
172
|
let firewall = a.firewall;
|
|
123
|
-
let
|
|
173
|
+
let order = (a.order ?? (a.random ? "random" : undefined));
|
|
174
|
+
let stopAtFirst = a["stop-at-first"];
|
|
124
175
|
let allPorts = a["all-ports"];
|
|
125
176
|
let docker = a.docker;
|
|
126
|
-
|
|
177
|
+
const last = loadLast();
|
|
178
|
+
// only the last results needs no countries: without a terminal, it goes without the picker
|
|
179
|
+
if (!countries && !(order === "last-only" && !tty)) {
|
|
127
180
|
if (!tty)
|
|
128
181
|
throw new Error("no terminal for the picker: pass countries with -c");
|
|
129
182
|
const saved = loadConfig();
|
|
@@ -134,9 +187,11 @@ async function main() {
|
|
|
134
187
|
speedTest: speedTest && (saved?.speed ?? false), // first run: connect-only
|
|
135
188
|
// already on in the app: show it on, since the scan won't turn it off
|
|
136
189
|
firewall: firewall || isFirewallOn(status) || (saved?.firewall ?? false),
|
|
137
|
-
|
|
190
|
+
order: order ?? saved?.order ?? "default",
|
|
191
|
+
stopAtFirst: stopAtFirst || (saved?.stopAtFirst ?? false),
|
|
138
192
|
allPorts: allPorts || (saved?.allPorts ?? false),
|
|
139
193
|
docker: docker || (saved?.docker ?? false),
|
|
194
|
+
last,
|
|
140
195
|
});
|
|
141
196
|
if (!choice)
|
|
142
197
|
return;
|
|
@@ -146,47 +201,71 @@ async function main() {
|
|
|
146
201
|
protocols ??= choice.protocols;
|
|
147
202
|
speedTest = choice.speed;
|
|
148
203
|
firewall = choice.firewall;
|
|
149
|
-
|
|
204
|
+
order = choice.order;
|
|
205
|
+
stopAtFirst = choice.stopAtFirst;
|
|
150
206
|
allPorts = choice.allPorts;
|
|
151
207
|
timeout = choice.timeout;
|
|
152
208
|
docker = choice.docker;
|
|
153
209
|
}
|
|
210
|
+
countries ??= [];
|
|
154
211
|
protocols ??= PROTOCOLS;
|
|
155
212
|
timeout ??= DEFAULT_TIMEOUT;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
213
|
+
order ??= "default";
|
|
214
|
+
const byLast = order === "last-first" || order === "last-only"; // a type by the last result: it needs a saved scan
|
|
215
|
+
if (byLast && !last)
|
|
216
|
+
throw new Error(`no saved scan yet: -o ${order} needs one, so run a scan with another scanning type first`);
|
|
217
|
+
const fromLast = byLast ? last : null; // the saved connections this scan uses
|
|
218
|
+
// the picker asked for the ports already: waiting for that keeps windscribe-cli free for the scan.
|
|
219
|
+
// The saved connections need them too, to tell a saved "tcp:443" from tcp on its default port
|
|
220
|
+
const lists = allPorts || !a.countries || fromLast ? await protocolPorts() : undefined;
|
|
221
|
+
if (allPorts && lists)
|
|
222
|
+
protocols = withPorts(protocols, lists);
|
|
223
|
+
const attempts = plan(order, countries, protocols, fromLast?.results, lists);
|
|
224
|
+
// the tables' columns: the protocols asked for, and the saved connections' own (only those, when only they are scanned)
|
|
225
|
+
const scanned = [...new Set([...(order === "last-only" ? [] : protocols), ...attempts.map((x) => x.protocol)])];
|
|
226
|
+
// a parallel scan is the containers' alone: the protocols they can't run (IKEv2, WireGuard on some kernels) aren't tried
|
|
227
|
+
let probers = [probe], skipped = new Set();
|
|
228
|
+
const inContainers = () => attempts.filter((x) => !skipped.has(x.protocol));
|
|
229
|
+
const nothingLeft = () => new Error(`the docker containers can't run ${[...skipped].join(" or ")}, and nothing else is selected:`
|
|
230
|
+
+ " scan without --docker (d in the picker)");
|
|
231
|
+
if (docker && attempts.length) {
|
|
232
|
+
skipped = notInContainers(scanned, loadDockerWireGuard()); // as far as the last parallel scan knows
|
|
233
|
+
if (!inContainers().length)
|
|
234
|
+
throw nothingLeft();
|
|
235
|
+
const started = await startParallel(inContainers().length);
|
|
169
236
|
if (!started)
|
|
170
237
|
return leaveScreen();
|
|
171
238
|
saveDockerWireGuard(started.wireguard);
|
|
172
239
|
probers = started.probers;
|
|
173
|
-
|
|
240
|
+
skipped = notInContainers(scanned, started.wireguard); // their kernel may not run WireGuard after all, or may now
|
|
241
|
+
if (!inContainers().length) {
|
|
242
|
+
await stopWorkers();
|
|
243
|
+
throw nothingLeft();
|
|
244
|
+
}
|
|
174
245
|
await windscribe("disconnect"); // a VPN connection here would carry the containers' traffic too
|
|
175
246
|
}
|
|
176
|
-
const scan = new Scan(countries,
|
|
247
|
+
const scan = new Scan(countries, scanned.filter((p) => !skipped.has(p)), { timeout, speedTest, only: inContainers(), stopAtFirst }, probers);
|
|
177
248
|
// not with containers: their traffic goes out through this computer, and the firewall would block it
|
|
178
|
-
const blocking = firewall && !
|
|
249
|
+
const blocking = firewall && !docker && (await blockInternet());
|
|
179
250
|
const view = tty ? showProgress(scan, blocking) : logProgress(scan);
|
|
180
251
|
await scan.run(view);
|
|
181
|
-
if (
|
|
252
|
+
if (docker)
|
|
182
253
|
await stopWorkers();
|
|
183
254
|
await restoreFirewall();
|
|
184
255
|
view.close();
|
|
185
256
|
for (const e of new Set(scan.errors))
|
|
186
257
|
console.error(yellow(e));
|
|
187
|
-
if (
|
|
258
|
+
if (skipped.size)
|
|
259
|
+
console.error(yellow(`not tried: ${[...skipped].join(", ")}, which the docker containers can't run (a scan without --docker can)`));
|
|
260
|
+
if (scan.found)
|
|
261
|
+
console.log(green("stopped at the first connection that held up (a speed test, then staying up)"));
|
|
262
|
+
else if (scan.stopping)
|
|
188
263
|
console.log(yellow("interrupted, showing partial results"));
|
|
189
|
-
const choices = report(scan.results, protocols, { all: a.all, speedTest });
|
|
264
|
+
const choices = report(scan.results, scan.protocols, { all: a.all, speedTest: speedTest || stopAtFirst }); // stop at the first measures speed
|
|
265
|
+
// its working connections replace the saved ones it tried, so what stopped working drops out; the saved ones it didn't try
|
|
266
|
+
// (other countries, or what a stopped scan didn't get to) stay, after its own; a scan where nothing worked keeps the last
|
|
267
|
+
const tried = new Set(scan.results.filter((r) => r.status !== "interrupted").map((r) => `${r.query} ${r.protocol}`));
|
|
268
|
+
saveLast(choices, undefined, tried);
|
|
190
269
|
await checkUpdate();
|
|
191
270
|
if (updateNotice())
|
|
192
271
|
console.error("\n" + versionLines().join("\n"));
|
package/dist/config.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
// The picker's selection,
|
|
1
|
+
// The picker's selection, what the last parallel scan found its containers can do, the last scan's working results, and the
|
|
2
|
+
// scan screen's table, remembered between runs.
|
|
2
3
|
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
4
|
import { homedir } from "node:os";
|
|
4
5
|
import { dirname, join } from "node:path";
|
|
6
|
+
import { ORDERS } from "./scan.js";
|
|
5
7
|
import { COUNTRIES, PROTOCOLS } from "./windscribe.js";
|
|
6
8
|
// false hides the speed test: every scan is connect-only, and a saved "on" reads as off
|
|
7
9
|
export const SPEED_TEST = true;
|
|
@@ -9,6 +11,8 @@ export const DEFAULT_TIMEOUT = 15;
|
|
|
9
11
|
export const MIN_TIMEOUT = 10; // seconds; shorter cuts off connections that would have worked
|
|
10
12
|
export const CONFIG = join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "windscout", "config.json");
|
|
11
13
|
const DOCKER_FILE = join(dirname(CONFIG), "docker.json"); // what the last parallel scan found its containers can do
|
|
14
|
+
export const LAST_FILE = join(dirname(CONFIG), "last.json"); // the last scan's working connections
|
|
15
|
+
const VIEW_FILE = join(dirname(CONFIG), "view.json"); // the table the scan screen was left on
|
|
12
16
|
/** The saved choice, minus anything this version doesn't offer; null on first run or an unreadable file */
|
|
13
17
|
export function loadConfig() {
|
|
14
18
|
try {
|
|
@@ -18,7 +22,8 @@ export function loadConfig() {
|
|
|
18
22
|
protocols: (c.protocols ?? []).filter((x) => PROTOCOLS.includes(x)),
|
|
19
23
|
speed: SPEED_TEST && c.speed !== false,
|
|
20
24
|
firewall: c.firewall === true,
|
|
21
|
-
|
|
25
|
+
order: ORDERS.includes(c.order) ? c.order : c.random === true ? "random" : "default", // before scanning types: random on or off
|
|
26
|
+
stopAtFirst: c.stopAtFirst === true,
|
|
22
27
|
allPorts: c.allPorts === true,
|
|
23
28
|
timeout: c.timeout >= MIN_TIMEOUT ? c.timeout : DEFAULT_TIMEOUT,
|
|
24
29
|
docker: c.docker === true,
|
|
@@ -51,6 +56,46 @@ export function saveDockerWireGuard(wireguard) {
|
|
|
51
56
|
}
|
|
52
57
|
catch { } // not being able to remember isn't worth failing the scan
|
|
53
58
|
}
|
|
59
|
+
/** The table the last scan was left on; the ranking until the user switches */
|
|
60
|
+
export function loadView() {
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(readFileSync(VIEW_FILE, "utf8")).view === "availability" ? "availability" : "ranking";
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return "ranking";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function saveView(view) {
|
|
69
|
+
try {
|
|
70
|
+
mkdirSync(dirname(VIEW_FILE), { recursive: true });
|
|
71
|
+
writeFileSync(VIEW_FILE, JSON.stringify({ view }) + "\n");
|
|
72
|
+
}
|
|
73
|
+
catch { } // not being able to remember isn't worth failing the scan
|
|
74
|
+
}
|
|
75
|
+
/** The last scan that found something; null when there's none or the file is unreadable */
|
|
76
|
+
export function loadLast() {
|
|
77
|
+
try {
|
|
78
|
+
const l = JSON.parse(readFileSync(LAST_FILE, "utf8"));
|
|
79
|
+
const results = (l.results ?? []).filter((r) => r?.status === "ok" && typeof r.query === "string" && typeof r.protocol === "string");
|
|
80
|
+
return results.length && typeof l.at === "number" ? { at: l.at, results } : null;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Saves a scan's working connections; a scan where nothing worked keeps the last one that found something (the network may
|
|
87
|
+
* just be down). With tried (the "query protocol" pairs the scan tried), the saved connections it didn't try stay, after its own */
|
|
88
|
+
export function saveLast(results, at = Date.now(), tried) {
|
|
89
|
+
if (!results.length)
|
|
90
|
+
return;
|
|
91
|
+
if (tried)
|
|
92
|
+
results = [...results, ...(loadLast()?.results ?? []).filter((r) => !tried.has(`${r.query} ${r.protocol}`))];
|
|
93
|
+
try {
|
|
94
|
+
mkdirSync(dirname(LAST_FILE), { recursive: true });
|
|
95
|
+
writeFileSync(LAST_FILE, JSON.stringify({ at, results }, null, 2) + "\n");
|
|
96
|
+
}
|
|
97
|
+
catch { } // not being able to remember isn't worth failing the scan
|
|
98
|
+
}
|
|
54
99
|
/** Deletes the Windscribe username and password that 0.6.0 saved for the docker containers: they log in with this computer's session now */
|
|
55
100
|
export function forgetLogin() {
|
|
56
101
|
try {
|
package/dist/docker.js
CHANGED
|
@@ -29,7 +29,7 @@ const REGISTRIES = [...(process.env.WINDSCOUT_REGISTRY_MIRRORS?.split(",").map((
|
|
|
29
29
|
// openresolv: the app sets the tunnel's DNS through resolvconf; without it, DNS goes nowhere once connected.
|
|
30
30
|
// The CMD first checks the kernel (WSL's builds tun and parts of nftables as modules): no tun or nftables, no tunnel, so
|
|
31
31
|
// the container stops saying so instead of scanning. The helper's WireGuard routing also puts a fib rule in an inet table,
|
|
32
|
-
// which Docker Desktop's own kernel (macOS, Windows without WSL) can't run: WS_WIREGUARD=0 then, and
|
|
32
|
+
// which Docker Desktop's own kernel (macOS, Windows without WSL) can't run: WS_WIREGUARD=0 then, and a parallel scan skips it.
|
|
33
33
|
// No systemd in a container: start the helper (root) like its unit would; the worker starts the app once it has the session
|
|
34
34
|
const DOCKERFILE = `FROM ${BASE_TAG}
|
|
35
35
|
ENV LANG=C.UTF-8
|
|
@@ -107,8 +107,8 @@ export async function dockerState() {
|
|
|
107
107
|
const WORKER_MEMORY = 250e6; // bytes per container: Windscribe's app and helper, a tunnel and node take ~120 MB
|
|
108
108
|
// ponytail: past this, more containers mostly add load on the one uplink, and WireGuard keys on the account; raise if real scans say so
|
|
109
109
|
const MAX_WORKERS = 16;
|
|
110
|
-
/** Protocols the containers can't do, which a parallel scan
|
|
111
|
-
*
|
|
110
|
+
/** Protocols the containers can't do, which a parallel scan skips: IKEv2 (Windscribe's Linux app has none), and WireGuard
|
|
111
|
+
* when their kernel can't run its routing (see DOCKERFILE) */
|
|
112
112
|
export const notInContainers = (protocols, wireguard) => new Set(protocols.filter((p) => /^ikev2(:|$)/.test(p) || (!wireguard && /^wireguard(:|$)/.test(p))));
|
|
113
113
|
/** Containers for this many attempts, when docker can run at most `most`: one per attempt; past that, the fewest that need no
|
|
114
114
|
* more rounds (17 attempts, 16 at most: 9 containers make 2 rounds, as 16 would), since fewer log in sooner and register fewer keys */
|
|
@@ -288,7 +288,7 @@ export async function startWorkers(n, session, onStatus) {
|
|
|
288
288
|
};
|
|
289
289
|
p.on("error", (e) => gone(e.message));
|
|
290
290
|
p.on("close", (code) => gone(lastLine(err) || `exited (${code})`));
|
|
291
|
-
const prober = (query, protocol, { timeout, speedTest, stopped = () => false, onStep = () => { } }) => new Promise((done, reject) => {
|
|
291
|
+
const prober = (query, protocol, { timeout, speedTest, steady = false, stopped = () => false, onStep = () => { } }) => new Promise((done, reject) => {
|
|
292
292
|
if (dead)
|
|
293
293
|
return reject(new Error(`container ${i + 1} stopped: ${dead}`));
|
|
294
294
|
let leave = null, over = false;
|
|
@@ -311,7 +311,7 @@ export async function startWorkers(n, session, onStatus) {
|
|
|
311
311
|
else if (m.result)
|
|
312
312
|
end(), done(m.result);
|
|
313
313
|
};
|
|
314
|
-
send({ probe: { query, protocol, timeout, speedTest } });
|
|
314
|
+
send({ probe: { query, protocol, timeout, speedTest, steady } });
|
|
315
315
|
});
|
|
316
316
|
fail = (why) => (onStatus(`container ${i + 1}: ${why}`), resolve(null));
|
|
317
317
|
on = (m) => {
|
package/dist/measure.js
CHANGED
|
@@ -9,7 +9,7 @@ setDefaultAutoSelectFamily(false);
|
|
|
9
9
|
setDefaultResultOrder("ipv4first");
|
|
10
10
|
const LATENCY_URL = "https://www.gstatic.com/generate_204";
|
|
11
11
|
const SPEED_URL = "https://speed.cloudflare.com/__down?bytes=10000000";
|
|
12
|
-
const SPEED_SECONDS = 15;
|
|
12
|
+
export const SPEED_SECONDS = 15;
|
|
13
13
|
/** Best round trip of a few tries, in ms; null if none got through */
|
|
14
14
|
export async function latencyMs(tries = 3) {
|
|
15
15
|
let best = null;
|