1import { spawn } from 'node:child_process';
2import * as interfacesModule from '../util/loadInterfaces.js';
3import * as config from '../controller/config.js';
4import fs from 'node:fs';
5import path from 'node:path';
6
7export default async (mode, serverId, serverUrl) => {
8 const binaryPath = mode === "ookla" ? './bin/speedtest' + (process.platform === "win32" ? ".exe" : "")
9 : mode === "libre" ? './bin/librespeed-cli' + (process.platform === "win32" ? ".exe" : "")
10 : './bin/cfspeedtest' + (process.platform === "win32" ? ".exe" : "");
11
12 if (!interfacesModule.interfaces) throw new Error("No interfaces found");
13
14 const currentInterface = await config.getValue("interface");
15 const interfaceIp = interfacesModule.interfaces[currentInterface];
16
17 const startTime = new Date().getTime();
18 let args;
19
20 if (mode === "ookla") {
21 args = ['--accept-license', '--accept-gdpr', '--format=json'];
22
23 if (process.platform === "win32") {
24 args.push('--ip=' + interfaceIp);
25 } else {
26 args.push('--interface=' + currentInterface);
27 }
28
29 if (serverId) args.push(`--server-id=${serverId}`);
30 } else if (mode === "libre") {
31 args = ['--json', '--duration=5', '--source=' + interfaceIp];
32 if (serverUrl) {
33 const customServerConfig = [{
34 id: 1,
35 name: "Custom Server",
36 server: serverUrl,
37 dlURL: "garbage.php",
38 ulURL: "empty.php",
39 pingURL: "empty.php",
40 getIpURL: "getIP.php"
41 }];
42 const tempJsonPath = path.join('data', 'servers', 'libre_custom.json');
43 fs.writeFileSync(tempJsonPath, JSON.stringify(customServerConfig));
44 args.push(`--local-json=${tempJsonPath}`);
45 args.push('--server=1');
46 } else if (serverId) {
47 args.push(`--server=${serverId}`);
48 }
49 } else if (mode === "cloudflare") {
50 args = ['--output-format=json'];
51
52 if (interfaceIp.includes(':')) {
53 args.push('--ipv6=' + interfaceIp);
54 } else {
55 args.push('--ipv4=' + interfaceIp);
56 }
57 }
58
59 let result = {};
60 let stdout = '';
61
62 const testProcess = spawn(binaryPath, args, {windowsHide: true});
63
64 testProcess.stderr.on('data', (buffer) => {
65 result.error = buffer.toString();
66 if (buffer.toString().includes("Too many requests")) {
67 result.error = "Too many requests. Please try again later";
68 }
69 });
70
71 testProcess.stdout.on('data', (buffer) => {
72 stdout += buffer.toString();
73 });
74
75 await new Promise((resolve, reject) => {
76 testProcess.on('error', e => reject({message: e}));
77 testProcess.on('exit', () => {
78 if (stdout.trim()) {
79 const lines = stdout.trim().split('\n');
80 for (const line of lines) {
81 if (!(line.startsWith("{") || line.startsWith("["))) continue;
82
83 let data = {};
84 try {
85 data = JSON.parse(line);
86 if (line.startsWith("[") && mode !== "cloudflare") data = data[0];
87 } catch (e) {
88 data.error = e.message;
89 console.error("JSON parse error:", e.message, "Line:", line);
90 continue;
91 }
92
93 if (data.error) result.error = data.error;
94
95 if ((mode === "ookla" && data.type === "result") || mode === "libre" || mode === "cloudflare") {
96 result = data;
97 }
98 }
99 }
100 resolve();
101 });
102 });
103
104 if (result.error) throw new Error(result.error);
105 return {...result, elapsed: new Date().getTime() - startTime};
106}