windscout 0.7.1 → 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 +85 -12
- package/dist/background.js +131 -0
- package/dist/cli.js +155 -112
- package/dist/config.js +25 -4
- package/dist/docker.js +120 -22
- package/dist/measure.js +12 -0
- package/dist/probe.js +50 -24
- package/dist/scan.js +1 -1
- package/dist/time.js +5 -0
- package/dist/tui/connect.js +29 -0
- package/dist/tui/picker.js +46 -33
- package/dist/tui/report.js +14 -4
- package/dist/tui/results.js +454 -0
- package/dist/tui/text.js +19 -0
- package/dist/watch.js +454 -0
- package/dist/windscribe.js +58 -10
- package/dist/worker.js +8 -6
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,30 +2,41 @@
|
|
|
2
2
|
// WindScout: find which Windscribe locations and protocols actually work from here.
|
|
3
3
|
// Drives the official windscribe-cli: connects to each country/protocol pair,
|
|
4
4
|
// measures latency and download speed through the tunnel, prints a ranked table.
|
|
5
|
+
// In a terminal, the results then stay on screen (tui/results.ts): connect, test again, and stay connected.
|
|
5
6
|
import { createInterface } from "node:readline";
|
|
6
7
|
import { spawnSync } from "node:child_process";
|
|
7
8
|
import { parseArgs } from "node:util";
|
|
8
|
-
import {
|
|
9
|
+
import { dockerChecker } from "./background.js";
|
|
10
|
+
import { CONFIG, DEFAULT_HOLD, DEFAULT_TIMEOUT, forgetLogin, LAST_FILE, loadConfig, loadDockerWireGuard, loadLast, MIN_HOLD, MIN_TIMEOUT, saveDockerWireGuard, saveLast, SPEED_TEST } from "./config.js";
|
|
9
11
|
import { notInContainers, stopWorkers } from "./docker.js";
|
|
10
|
-
import {
|
|
12
|
+
import { disconnect, MIN_MBPS, probe } from "./probe.js";
|
|
11
13
|
import { ORDERS, plan, Scan } from "./scan.js";
|
|
14
|
+
import { connectFrom } from "./tui/connect.js";
|
|
12
15
|
import { startParallel } from "./tui/docker.js";
|
|
13
16
|
import { pick } from "./tui/picker.js";
|
|
14
17
|
import { logProgress, showProgress } from "./tui/progress.js";
|
|
15
|
-
import {
|
|
18
|
+
import { report } from "./tui/report.js";
|
|
19
|
+
import { showResults } from "./tui/results.js";
|
|
16
20
|
import { leaveScreen, out } from "./tui/terminal.js";
|
|
17
|
-
import {
|
|
18
|
-
import { duration
|
|
21
|
+
import { green, yellow } from "./tui/text.js";
|
|
22
|
+
import { duration } from "./time.js";
|
|
19
23
|
import { canUpdate, checkUpdate, runUpdate, updateNotice, versionLines } from "./update.js";
|
|
20
|
-
import { blockInternet, CLI, isFirewallOn, protocolPorts, PROTOCOLS, restoreFirewall, waitForLogin,
|
|
24
|
+
import { blockInternet, CLI, isFirewallOn, protocolPorts, PROTOCOLS, restoreFirewall, waitForLogin, withPorts } from "./windscribe.js";
|
|
21
25
|
import { worker } from "./worker.js";
|
|
26
|
+
import { CHECK_EVERY } from "./watch.js";
|
|
22
27
|
const HELP = `Usage: windscout [options]
|
|
23
28
|
windscout connect [-t SEC]
|
|
24
29
|
|
|
25
30
|
Find which Windscribe locations and protocols work from your network, ranked by ${SPEED_TEST ? "speed" : "latency"}.
|
|
26
31
|
Without -c it opens an interactive country picker (your selection is remembered).
|
|
32
|
+
In a terminal, the results then stay on screen until you quit: connect to one (the screen shows
|
|
33
|
+
where you're connected, live), test them all again for speed or latency (what doesn't connect
|
|
34
|
+
goes to the end of the list), and watch the connection: when it drops, windscout connects you to
|
|
35
|
+
the next best. While watching, docker containers can check the list every ${duration(CHECK_EVERY)} without touching
|
|
36
|
+
your VPN (b there; latency only). n there starts a new scan; q quits and leaves the VPN as it is.
|
|
27
37
|
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
|
|
38
|
+
them first or only them, and windscout connect connects to the best of them that still works
|
|
39
|
+
(in a terminal, on the results screen).
|
|
29
40
|
|
|
30
41
|
Options:
|
|
31
42
|
-c, --countries LIST comma-separated ISO codes, city names or nicknames (skips the picker)
|
|
@@ -38,7 +49,9 @@ ${SPEED_TEST ? " -n, --no-speed connect-only: skip the download test, ra
|
|
|
38
49
|
result's working connections among them first, then the rest) or last-only (only those,
|
|
39
50
|
whatever -c and -p say; needs no -c)
|
|
40
51
|
-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,
|
|
52
|
+
${MIN_MBPS} Mbps, staying connected with internet through it (-H), a speed test again, and a reconnect
|
|
53
|
+
-H, --hold SEC with -w: seconds each working connection has to stay connected, at least ${MIN_HOLD}
|
|
54
|
+
(default: saved, or ${DEFAULT_HOLD})
|
|
42
55
|
-d, --docker scan in parallel, in docker containers (one per attempt, up to 16, as docker's CPUs and memory allow)
|
|
43
56
|
-v, --version show the installed version (and offer to update if a newer one is out)
|
|
44
57
|
-h, --help show this help
|
|
@@ -59,36 +72,8 @@ const ask = (question, instant = []) => new Promise((resolve) => {
|
|
|
59
72
|
rl.on("close", () => (process.stdin.off("keypress", onKey), resolve(null)));
|
|
60
73
|
rl.question(question, (a) => (resolve(a.trim()), rl.close()));
|
|
61
74
|
});
|
|
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 */
|
|
75
|
-
async function offerConnect(choices, timeout) {
|
|
76
|
-
const others = choices.length > 2 ? `, 1-${choices.length - 1} = other` : choices.length > 1 ? ", 1 = other" : "";
|
|
77
|
-
for (;;) {
|
|
78
|
-
const a = await ask(`\nconnect to # (enter or 0 = best${others}, q = quit): `, ["0", "q"]);
|
|
79
|
-
if (a === null || /^q(uit)?$/i.test(a))
|
|
80
|
-
return;
|
|
81
|
-
const r = a === "" ? choices[0] : /^\d+$/.test(a) ? choices[Number(a)] : undefined;
|
|
82
|
-
if (!r) {
|
|
83
|
-
console.log(yellow(`no #${a} in the tables`));
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
await connectTo(r, timeout);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
75
|
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 */
|
|
76
|
+
/** windscout connect without a terminal: the last result's connections, best first, until one connects */
|
|
92
77
|
async function connectLast(timeout) {
|
|
93
78
|
const last = loadLast();
|
|
94
79
|
if (!last) {
|
|
@@ -98,13 +83,11 @@ async function connectLast(timeout) {
|
|
|
98
83
|
}
|
|
99
84
|
await waitForLogin();
|
|
100
85
|
await disconnect(); // a connection already up would read as the first one connecting
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
86
|
+
const i = await connectFrom(last.results, 0, timeout);
|
|
87
|
+
if (i < 0) {
|
|
88
|
+
console.log(yellow(`\nnone of the last result's ${last.results.length} connection${last.results.length === 1 ? "" : "s"} works now: ${RESCAN_HINT}`));
|
|
89
|
+
process.exitCode = 1;
|
|
105
90
|
}
|
|
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;
|
|
108
91
|
}
|
|
109
92
|
/** u in the picker: update, then run the new version in this one's place, with the same arguments */
|
|
110
93
|
function updateAndRestart() {
|
|
@@ -117,63 +100,12 @@ function updateAndRestart() {
|
|
|
117
100
|
console.log(green("updated · restarting windscout"));
|
|
118
101
|
process.exitCode = spawnSync(process.execPath, [...process.execArgv, ...process.argv.slice(1)], { stdio: "inherit" }).status ?? 1;
|
|
119
102
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
options: {
|
|
124
|
-
countries: { type: "string", short: "c" },
|
|
125
|
-
protocols: { type: "string", short: "p" },
|
|
126
|
-
"all-ports": { type: "boolean", short: "P", default: false },
|
|
127
|
-
timeout: { type: "string", short: "t" },
|
|
128
|
-
all: { type: "boolean", short: "a", default: false },
|
|
129
|
-
help: { type: "boolean", short: "h", default: false },
|
|
130
|
-
version: { type: "boolean", short: "v", default: false },
|
|
131
|
-
"no-speed": { type: "boolean", short: "n", default: false },
|
|
132
|
-
firewall: { type: "boolean", short: "f", default: false },
|
|
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
|
|
136
|
-
docker: { type: "boolean", short: "d", default: false },
|
|
137
|
-
worker: { type: "boolean", default: false }, // in a parallel scan's container
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
if (a.worker) {
|
|
141
|
-
await worker();
|
|
142
|
-
process.exit(); // its stdin may still be open: its app didn't log in
|
|
143
|
-
}
|
|
144
|
-
forgetLogin();
|
|
145
|
-
if (a.help)
|
|
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");
|
|
152
|
-
checkUpdate(); // runs while the app starts and the user picks, before the VPN starts switching
|
|
153
|
-
if (a.version) {
|
|
154
|
-
await checkUpdate();
|
|
155
|
-
console.log(versionLines().join("\n"));
|
|
156
|
-
if (canUpdate() && process.stdin.isTTY && out.isTTY && /^y(es)?$/i.test((await ask("update now? (y/n) ")) ?? ""))
|
|
157
|
-
process.exitCode = runUpdate();
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
let timeout = a.timeout ? Number(a.timeout) : undefined;
|
|
161
|
-
if (timeout !== undefined && !(timeout >= MIN_TIMEOUT))
|
|
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);
|
|
103
|
+
/** One scan: the picker (unless -c), then the scan, its report, and its working connections saved. Resolves to them, best
|
|
104
|
+
* first, and the timeout it used; null when the picker was left, "update" when u was pressed there */
|
|
105
|
+
async function scanOnce(f, tty) {
|
|
167
106
|
const status = await waitForLogin();
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
let protocols = a.protocols ? list(a.protocols) : undefined;
|
|
171
|
-
let speedTest = SPEED_TEST && !a["no-speed"];
|
|
172
|
-
let firewall = a.firewall;
|
|
173
|
-
let order = (a.order ?? (a.random ? "random" : undefined));
|
|
174
|
-
let stopAtFirst = a["stop-at-first"];
|
|
175
|
-
let allPorts = a["all-ports"];
|
|
176
|
-
let docker = a.docker;
|
|
107
|
+
let { countries, protocols, speedTest, firewall, order, stopAtFirst, allPorts, docker, timeout, hold } = f;
|
|
108
|
+
speedTest &&= SPEED_TEST;
|
|
177
109
|
const last = loadLast();
|
|
178
110
|
// only the last results needs no countries: without a terminal, it goes without the picker
|
|
179
111
|
if (!countries && !(order === "last-only" && !tty)) {
|
|
@@ -189,20 +121,20 @@ async function main() {
|
|
|
189
121
|
firewall: firewall || isFirewallOn(status) || (saved?.firewall ?? false),
|
|
190
122
|
order: order ?? saved?.order ?? "default",
|
|
191
123
|
stopAtFirst: stopAtFirst || (saved?.stopAtFirst ?? false),
|
|
124
|
+
hold: hold ?? saved?.hold ?? DEFAULT_HOLD,
|
|
192
125
|
allPorts: allPorts || (saved?.allPorts ?? false),
|
|
193
126
|
docker: docker || (saved?.docker ?? false),
|
|
194
127
|
last,
|
|
195
128
|
});
|
|
196
|
-
if (!choice)
|
|
197
|
-
return;
|
|
198
|
-
if (choice === "update")
|
|
199
|
-
return updateAndRestart();
|
|
129
|
+
if (!choice || choice === "update")
|
|
130
|
+
return choice;
|
|
200
131
|
countries = choice.countries;
|
|
201
132
|
protocols ??= choice.protocols;
|
|
202
133
|
speedTest = choice.speed;
|
|
203
134
|
firewall = choice.firewall;
|
|
204
135
|
order = choice.order;
|
|
205
136
|
stopAtFirst = choice.stopAtFirst;
|
|
137
|
+
hold = choice.hold;
|
|
206
138
|
allPorts = choice.allPorts;
|
|
207
139
|
timeout = choice.timeout;
|
|
208
140
|
docker = choice.docker;
|
|
@@ -210,6 +142,7 @@ async function main() {
|
|
|
210
142
|
countries ??= [];
|
|
211
143
|
protocols ??= PROTOCOLS;
|
|
212
144
|
timeout ??= DEFAULT_TIMEOUT;
|
|
145
|
+
hold ??= DEFAULT_HOLD;
|
|
213
146
|
order ??= "default";
|
|
214
147
|
const byLast = order === "last-first" || order === "last-only"; // a type by the last result: it needs a saved scan
|
|
215
148
|
if (byLast && !last)
|
|
@@ -217,7 +150,7 @@ async function main() {
|
|
|
217
150
|
const fromLast = byLast ? last : null; // the saved connections this scan uses
|
|
218
151
|
// the picker asked for the ports already: waiting for that keeps windscribe-cli free for the scan.
|
|
219
152
|
// The saved connections need them too, to tell a saved "tcp:443" from tcp on its default port
|
|
220
|
-
const lists = allPorts || !
|
|
153
|
+
const lists = allPorts || !f.countries || fromLast ? await protocolPorts() : undefined;
|
|
221
154
|
if (allPorts && lists)
|
|
222
155
|
protocols = withPorts(protocols, lists);
|
|
223
156
|
const attempts = plan(order, countries, protocols, fromLast?.results, lists);
|
|
@@ -234,7 +167,7 @@ async function main() {
|
|
|
234
167
|
throw nothingLeft();
|
|
235
168
|
const started = await startParallel(inContainers().length);
|
|
236
169
|
if (!started)
|
|
237
|
-
return leaveScreen();
|
|
170
|
+
return leaveScreen(), null;
|
|
238
171
|
saveDockerWireGuard(started.wireguard);
|
|
239
172
|
probers = started.probers;
|
|
240
173
|
skipped = notInContainers(scanned, started.wireguard); // their kernel may not run WireGuard after all, or may now
|
|
@@ -242,9 +175,12 @@ async function main() {
|
|
|
242
175
|
await stopWorkers();
|
|
243
176
|
throw nothingLeft();
|
|
244
177
|
}
|
|
245
|
-
await windscribe("disconnect"); // a VPN connection here would carry the containers' traffic too
|
|
246
178
|
}
|
|
247
|
-
const scan = new Scan(countries, scanned.filter((p) => !skipped.has(p)), { timeout, speedTest, only: inContainers(), stopAtFirst }, probers);
|
|
179
|
+
const scan = new Scan(countries, scanned.filter((p) => !skipped.has(p)), { timeout, speedTest, only: inContainers(), stopAtFirst: stopAtFirst ? hold : 0 }, probers);
|
|
180
|
+
// the scan switches the VPN anyway: one still up (from before, or the results screen) would read as its first attempt
|
|
181
|
+
// connecting, and with containers, it would carry their traffic too
|
|
182
|
+
if (scan.total)
|
|
183
|
+
await disconnect();
|
|
248
184
|
// not with containers: their traffic goes out through this computer, and the firewall would block it
|
|
249
185
|
const blocking = firewall && !docker && (await blockInternet());
|
|
250
186
|
const view = tty ? showProgress(scan, blocking) : logProgress(scan);
|
|
@@ -258,19 +194,126 @@ async function main() {
|
|
|
258
194
|
if (skipped.size)
|
|
259
195
|
console.error(yellow(`not tried: ${[...skipped].join(", ")}, which the docker containers can't run (a scan without --docker can)`));
|
|
260
196
|
if (scan.found)
|
|
261
|
-
console.log(green(
|
|
197
|
+
console.log(green(`stopped at the first connection that held up: a speed test, ${duration(hold)} connected, a speed test again and a reconnect`));
|
|
262
198
|
else if (scan.stopping)
|
|
263
199
|
console.log(yellow("interrupted, showing partial results"));
|
|
264
|
-
|
|
200
|
+
// a slower one is never stopped at, so it isn't checked
|
|
201
|
+
else if (stopAtFirst && scan.results.some((r) => r.status === "ok"))
|
|
202
|
+
console.log(yellow(`none held up at ${MIN_MBPS} Mbps or more: the connections below are slower, and weren't checked for stability`));
|
|
203
|
+
const results = report(scan.results, scan.protocols, { all: f.all, speedTest: speedTest || stopAtFirst }); // stop at the first measures speed
|
|
265
204
|
// its working connections replace the saved ones it tried, so what stopped working drops out; the saved ones it didn't try
|
|
266
205
|
// (other countries, or what a stopped scan didn't get to) stay, after its own; a scan where nothing worked keeps the last
|
|
267
206
|
const tried = new Set(scan.results.filter((r) => r.status !== "interrupted").map((r) => `${r.query} ${r.protocol}`));
|
|
268
|
-
saveLast(
|
|
207
|
+
saveLast(results, undefined, tried);
|
|
269
208
|
await checkUpdate();
|
|
270
209
|
if (updateNotice())
|
|
271
210
|
console.error("\n" + versionLines().join("\n"));
|
|
272
|
-
|
|
273
|
-
|
|
211
|
+
return { results, timeout };
|
|
212
|
+
}
|
|
213
|
+
async function main() {
|
|
214
|
+
const { values: a, positionals } = parseArgs({
|
|
215
|
+
allowPositionals: true,
|
|
216
|
+
options: {
|
|
217
|
+
countries: { type: "string", short: "c" },
|
|
218
|
+
protocols: { type: "string", short: "p" },
|
|
219
|
+
"all-ports": { type: "boolean", short: "P", default: false },
|
|
220
|
+
timeout: { type: "string", short: "t" },
|
|
221
|
+
all: { type: "boolean", short: "a", default: false },
|
|
222
|
+
help: { type: "boolean", short: "h", default: false },
|
|
223
|
+
version: { type: "boolean", short: "v", default: false },
|
|
224
|
+
"no-speed": { type: "boolean", short: "n", default: false },
|
|
225
|
+
firewall: { type: "boolean", short: "f", default: false },
|
|
226
|
+
order: { type: "string", short: "o" },
|
|
227
|
+
"stop-at-first": { type: "boolean", short: "w", default: false },
|
|
228
|
+
hold: { type: "string", short: "H" },
|
|
229
|
+
random: { type: "boolean", short: "r", default: false }, // from before scanning types: -o random
|
|
230
|
+
docker: { type: "boolean", short: "d", default: false },
|
|
231
|
+
worker: { type: "boolean", default: false }, // in a parallel scan's container
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
if (a.worker) {
|
|
235
|
+
await worker();
|
|
236
|
+
process.exit(); // its stdin may still be open: its app didn't log in
|
|
237
|
+
}
|
|
238
|
+
forgetLogin();
|
|
239
|
+
if (a.help)
|
|
240
|
+
return console.log(HELP);
|
|
241
|
+
const [subcommand, ...extra] = positionals;
|
|
242
|
+
if (subcommand !== undefined && (subcommand !== "connect" || extra.length))
|
|
243
|
+
throw new Error(`unknown command '${positionals.join(" ")}': see --help`);
|
|
244
|
+
if (subcommand === "connect" && Object.entries(a).some(([k, v]) => k !== "timeout" && v))
|
|
245
|
+
throw new Error("windscout connect takes only -t: see --help");
|
|
246
|
+
checkUpdate(); // runs while the app starts and the user picks, before the VPN starts switching
|
|
247
|
+
if (a.version) {
|
|
248
|
+
await checkUpdate();
|
|
249
|
+
console.log(versionLines().join("\n"));
|
|
250
|
+
if (canUpdate() && process.stdin.isTTY && out.isTTY && /^y(es)?$/i.test((await ask("update now? (y/n) ")) ?? ""))
|
|
251
|
+
process.exitCode = runUpdate();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const timeout = a.timeout ? Number(a.timeout) : undefined;
|
|
255
|
+
if (timeout !== undefined && !(timeout >= MIN_TIMEOUT))
|
|
256
|
+
throw new Error(`--timeout must be at least ${MIN_TIMEOUT} seconds`);
|
|
257
|
+
const hold = a.hold ? Number(a.hold) : undefined;
|
|
258
|
+
if (hold !== undefined && !(hold >= MIN_HOLD))
|
|
259
|
+
throw new Error(`--hold must be at least ${MIN_HOLD} seconds`);
|
|
260
|
+
if (a.order !== undefined && !ORDERS.includes(a.order))
|
|
261
|
+
throw new Error(`--order must be one of: ${ORDERS.join(", ")}`);
|
|
262
|
+
const tty = Boolean(process.stdin.isTTY && out.isTTY);
|
|
263
|
+
// a later scan: the picker, with what was saved
|
|
264
|
+
const again = { speedTest: true, firewall: false, stopAtFirst: false, allPorts: false, docker: false, all: a.all };
|
|
265
|
+
let flags = {
|
|
266
|
+
countries: a.countries ? list(a.countries) : undefined,
|
|
267
|
+
protocols: a.protocols ? list(a.protocols) : undefined,
|
|
268
|
+
timeout,
|
|
269
|
+
hold,
|
|
270
|
+
order: (a.order ?? (a.random ? "random" : undefined)),
|
|
271
|
+
speedTest: !a["no-speed"],
|
|
272
|
+
firewall: a.firewall,
|
|
273
|
+
stopAtFirst: a["stop-at-first"],
|
|
274
|
+
allPorts: a["all-ports"],
|
|
275
|
+
docker: a.docker,
|
|
276
|
+
all: a.all,
|
|
277
|
+
};
|
|
278
|
+
if (subcommand === "connect") {
|
|
279
|
+
const t = timeout ?? loadConfig()?.timeout ?? DEFAULT_TIMEOUT;
|
|
280
|
+
if (!tty)
|
|
281
|
+
return connectLast(t);
|
|
282
|
+
const last = loadLast();
|
|
283
|
+
if (!last) {
|
|
284
|
+
console.log(yellow("no saved scan yet: run windscout to scan first"));
|
|
285
|
+
process.exitCode = 1;
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
await waitForLogin();
|
|
289
|
+
// the best that still works, on the results screen, which keeps you connected
|
|
290
|
+
const to = await showResults({ entries: last.results, at: last.at, timeout: t, saved: true, connect: true, checker: () => dockerChecker(t) });
|
|
291
|
+
if (to === "quit")
|
|
292
|
+
return;
|
|
293
|
+
flags = again;
|
|
294
|
+
}
|
|
295
|
+
for (;;) {
|
|
296
|
+
const found = await scanOnce(flags, tty);
|
|
297
|
+
if (found === null)
|
|
298
|
+
return;
|
|
299
|
+
if (found === "update")
|
|
300
|
+
return updateAndRestart();
|
|
301
|
+
if (!tty)
|
|
302
|
+
return;
|
|
303
|
+
// nothing worked: the saved ones, which may work again; the screen still lets you scan again
|
|
304
|
+
const last = found.results.length ? null : loadLast();
|
|
305
|
+
const to = await showResults({
|
|
306
|
+
entries: last?.results ?? found.results,
|
|
307
|
+
at: last?.at ?? Date.now(),
|
|
308
|
+
timeout: found.timeout,
|
|
309
|
+
saved: !!last,
|
|
310
|
+
note: last ? "nothing connected in this scan: these are your saved connections, from earlier" : undefined,
|
|
311
|
+
checker: () => dockerChecker(found.timeout),
|
|
312
|
+
});
|
|
313
|
+
if (to === "quit")
|
|
314
|
+
return;
|
|
315
|
+
flags = again;
|
|
316
|
+
}
|
|
274
317
|
}
|
|
275
318
|
main().catch(async (e) => {
|
|
276
319
|
leaveScreen();
|
package/dist/config.js
CHANGED
|
@@ -9,6 +9,8 @@ import { COUNTRIES, PROTOCOLS } from "./windscribe.js";
|
|
|
9
9
|
export const SPEED_TEST = true;
|
|
10
10
|
export const DEFAULT_TIMEOUT = 15;
|
|
11
11
|
export const MIN_TIMEOUT = 10; // seconds; shorter cuts off connections that would have worked
|
|
12
|
+
export const DEFAULT_HOLD = 30;
|
|
13
|
+
export const MIN_HOLD = 10; // seconds; shorter says little about how long a connection lasts
|
|
12
14
|
export const CONFIG = join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "windscout", "config.json");
|
|
13
15
|
const DOCKER_FILE = join(dirname(CONFIG), "docker.json"); // what the last parallel scan found its containers can do
|
|
14
16
|
export const LAST_FILE = join(dirname(CONFIG), "last.json"); // the last scan's working connections
|
|
@@ -24,6 +26,7 @@ export function loadConfig() {
|
|
|
24
26
|
firewall: c.firewall === true,
|
|
25
27
|
order: ORDERS.includes(c.order) ? c.order : c.random === true ? "random" : "default", // before scanning types: random on or off
|
|
26
28
|
stopAtFirst: c.stopAtFirst === true,
|
|
29
|
+
hold: c.hold >= MIN_HOLD ? c.hold : DEFAULT_HOLD,
|
|
27
30
|
allPorts: c.allPorts === true,
|
|
28
31
|
timeout: c.timeout >= MIN_TIMEOUT ? c.timeout : DEFAULT_TIMEOUT,
|
|
29
32
|
docker: c.docker === true,
|
|
@@ -33,13 +36,30 @@ export function loadConfig() {
|
|
|
33
36
|
return null;
|
|
34
37
|
}
|
|
35
38
|
}
|
|
39
|
+
/** Saves these settings over the saved ones: the rest of the file stays (the results screen's, the picker's) */
|
|
36
40
|
export function saveConfig(c) {
|
|
41
|
+
let saved = {};
|
|
42
|
+
try {
|
|
43
|
+
saved = JSON.parse(readFileSync(CONFIG, "utf8"));
|
|
44
|
+
}
|
|
45
|
+
catch { } // first run, or unreadable: start over
|
|
37
46
|
try {
|
|
38
47
|
mkdirSync(dirname(CONFIG), { recursive: true });
|
|
39
|
-
writeFileSync(CONFIG, JSON.stringify(c, null, 2) + "\n");
|
|
48
|
+
writeFileSync(CONFIG, JSON.stringify({ ...saved, ...c }, null, 2) + "\n");
|
|
40
49
|
}
|
|
41
50
|
catch { } // not being able to remember isn't worth failing the scan
|
|
42
51
|
}
|
|
52
|
+
/** The results screen's saved settings: watching on until turned off; background checks as docker is for parallel scans until
|
|
53
|
+
* turned on or off on that screen */
|
|
54
|
+
export function loadSettings() {
|
|
55
|
+
try {
|
|
56
|
+
const c = JSON.parse(readFileSync(CONFIG, "utf8"));
|
|
57
|
+
return { watch: c.watch !== false, background: typeof c.background === "boolean" ? c.background : c.docker === true };
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return { watch: true, background: false };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
43
63
|
/** Whether WireGuard worked in the docker containers last time (yes until a scan finds otherwise), for the picker's estimate */
|
|
44
64
|
export function loadDockerWireGuard() {
|
|
45
65
|
try {
|
|
@@ -84,12 +104,13 @@ export function loadLast() {
|
|
|
84
104
|
}
|
|
85
105
|
}
|
|
86
106
|
/** 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
|
|
107
|
+
* just be down). With tried (the "query protocol" pairs the scan tried), the saved connections it didn't try stay, after its own
|
|
108
|
+
* working ones and before the ones whose last check failed */
|
|
88
109
|
export function saveLast(results, at = Date.now(), tried) {
|
|
89
110
|
if (!results.length)
|
|
90
111
|
return;
|
|
91
|
-
|
|
92
|
-
|
|
112
|
+
const all = [...results, ...(tried ? (loadLast()?.results ?? []).filter((r) => !tried.has(`${r.query} ${r.protocol}`)) : [])];
|
|
113
|
+
results = [...all.filter((r) => !r.failed), ...all.filter((r) => r.failed)];
|
|
93
114
|
try {
|
|
94
115
|
mkdirSync(dirname(LAST_FILE), { recursive: true });
|
|
95
116
|
writeFileSync(LAST_FILE, JSON.stringify({ at, results }, null, 2) + "\n");
|