mirror of
https://github.com/gnmyt/myspeed.git
synced 2026-02-13 17:19:12 -06:00
Merge pull request #677 from gnmyt/features/multiple-providers
🗃️ Support für LibreSpeed & Cloudflare Speed
This commit is contained in:
@@ -15,14 +15,16 @@
|
||||
"wrong": "The password you entered is incorrect",
|
||||
"unlock": "Unlock"
|
||||
},
|
||||
"accept": {
|
||||
"title": "We need your permission",
|
||||
"description": "We use services from Ookla. By clicking <Bold>Accept</Bold>, you acknowledge that you have read and agree to Ookla's <EULA>EULA</EULA>, <Privacy>Privacy Statement</Privacy> and <Terms>Terms of Use</Terms>.",
|
||||
"button": "Accept"
|
||||
},
|
||||
"api": {
|
||||
"title": "API not reachable",
|
||||
"description": "MySpeed could not reach the API of this instance. Please try again later."
|
||||
},
|
||||
"provider": {
|
||||
"server": "Server",
|
||||
"server_id": "Server ID",
|
||||
"choose_automatically": "Choose automatically",
|
||||
"ookla_license": "I have read and accept the <Eula>EULA</Eula>, <GDPR>privacy policy</GDPR> and <TOS>terms of service</TOS> of Ookla.",
|
||||
"cloudflare_note": "Cloudflare does not require any additional settings"
|
||||
}
|
||||
},
|
||||
"dropdown": {
|
||||
@@ -35,7 +37,7 @@
|
||||
"upload": "Optimal up-speed",
|
||||
"download": "Optimal down-speed",
|
||||
"recommendations": "Recommendations",
|
||||
"server": "Change Server",
|
||||
"change_provider": "Change provider",
|
||||
"password": "Change password",
|
||||
"cron": "Set frequency",
|
||||
"time": "Set period",
|
||||
@@ -80,10 +82,8 @@
|
||||
"download_placeholder": "Down speed (Mbps)",
|
||||
"recommendations_title": "Optimal recommendations",
|
||||
"recommendations_set": "Set automatic recommendations?",
|
||||
"server_title": "Set speedtest server",
|
||||
"provider_title": "Set speedtest provider",
|
||||
"manually": "Set manually",
|
||||
"manual_server_title": "Set speedtest server",
|
||||
"manual_server_id": "Server ID",
|
||||
"new_password": "Set a new password",
|
||||
"password_placeholder": "New password",
|
||||
"password_removed": "The password lock has been removed and the set password has been removed.",
|
||||
|
||||
@@ -16,10 +16,9 @@ import {
|
||||
faPause,
|
||||
faPingPongPaddleBall,
|
||||
faPlay,
|
||||
faServer,
|
||||
faWandMagicSparkles,
|
||||
faCheck,
|
||||
faExclamationTriangle
|
||||
faExclamationTriangle, faSliders
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import {ConfigContext} from "@/common/contexts/Config";
|
||||
import {StatusContext} from "@/common/contexts/Status";
|
||||
@@ -36,6 +35,7 @@ import {ToastNotificationContext} from "@/common/contexts/ToastNotification";
|
||||
import {NodeContext} from "@/common/contexts/Node";
|
||||
import {IntegrationDialog} from "@/common/components/IntegrationDialog";
|
||||
import LanguageDialog from "@/common/components/LanguageDialog";
|
||||
import ProviderDialog from "@/common/components/ProviderDialog";
|
||||
|
||||
let icon;
|
||||
|
||||
@@ -65,6 +65,7 @@ function DropdownComponent() {
|
||||
const [showViewDialog, setShowViewDialog] = useState(false);
|
||||
const [showIntegrationDialog, setShowIntegrationDialog] = useState(false);
|
||||
const [showLanguageDialog, setShowLanguageDialog] = useState(false);
|
||||
const [showProviderDialog, setShowProviderDialog] = useState(false);
|
||||
const ref = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -130,19 +131,6 @@ function DropdownComponent() {
|
||||
} else setDialog({title: t("update.recommendations_title"), description: t("info.recommendations_error"), buttonText: t("dialog.okay")});
|
||||
}
|
||||
|
||||
const updateServer = () => patchDialog("serverId", async (value) => ({
|
||||
title: t("update.server_title"),
|
||||
select: true,
|
||||
selectOptions: await jsonRequest("/info/server"),
|
||||
unsetButton: t("update.manually"),
|
||||
onClear: updateServerManually,
|
||||
value
|
||||
}));
|
||||
|
||||
const updateServerManually = () => patchDialog("serverId", (value) => ({
|
||||
title: t("update.manual_server_title"), placeholder: t("update.manual_server_id"), type: "number", value: value,
|
||||
}));
|
||||
|
||||
const updatePassword = async () => {
|
||||
const passwordSet = currentNode !== 0 ? findNode(currentNode).password : localStorage.getItem("password") != null;
|
||||
|
||||
@@ -239,7 +227,7 @@ function DropdownComponent() {
|
||||
{run: updateDownload, icon: faArrowDown, text: t("dropdown.download")},
|
||||
{run: recommendedSettings, icon: faWandMagicSparkles, text: t("dropdown.recommendations")},
|
||||
{hr: true, key: 1},
|
||||
{run: updateServer, icon: faServer, text: t("dropdown.server")},
|
||||
{run: () => setShowProviderDialog(true), icon: faSliders, text: t("dropdown.change_provider")},
|
||||
{run: updatePassword, icon: faKey, text: t("dropdown.password"), previewHidden: true},
|
||||
{run: updateCron, icon: faClock, text: t("dropdown.cron")},
|
||||
{run: exportDialog, icon: faFileExport, text: t("dropdown.export")},
|
||||
@@ -258,6 +246,7 @@ function DropdownComponent() {
|
||||
{showViewDialog && <ViewDialog onClose={() => setShowViewDialog(false)}/>}
|
||||
{showIntegrationDialog && <IntegrationDialog onClose={() => setShowIntegrationDialog(false)}/>}
|
||||
{showLanguageDialog && <LanguageDialog onClose={() => setShowLanguageDialog(false)}/>}
|
||||
{showProviderDialog && <ProviderDialog onClose={() => setShowProviderDialog(false)}/>}
|
||||
<div className="dropdown dropdown-invisible" id="dropdown" ref={ref}>
|
||||
<div className="dropdown-content">
|
||||
<h2>{t("dropdown.settings")}</h2>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
border-radius: 0.5rem
|
||||
|
||||
&:hover
|
||||
background-color: $light-gray
|
||||
background-color: $darker-gray
|
||||
|
||||
img
|
||||
width: 2rem
|
||||
@@ -38,6 +38,9 @@
|
||||
background-color: $light-gray
|
||||
color: $white
|
||||
|
||||
&:hover
|
||||
background-color: $light-gray
|
||||
|
||||
@media screen and (max-height: 425px)
|
||||
.language-chooser-dialog
|
||||
height: 15rem
|
||||
|
||||
138
client/src/common/components/ProviderDialog/ProviderDialog.jsx
Normal file
138
client/src/common/components/ProviderDialog/ProviderDialog.jsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import {DialogContext, DialogProvider} from "@/common/contexts/Dialog";
|
||||
import {t} from "i18next";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faClose} from "@fortawesome/free-solid-svg-icons";
|
||||
import "./styles.sass";
|
||||
import React, {useContext, useEffect, useState} from "react";
|
||||
import OoklaImage from "./assets/img/ookla.webp";
|
||||
import LibreImage from "./assets/img/libre.webp";
|
||||
import CloudflareImage from "./assets/img/cloudflare.webp";
|
||||
import {jsonRequest, patchRequest} from "@/common/utils/RequestUtil";
|
||||
import {Trans} from "react-i18next";
|
||||
import {ConfigContext} from "@/common/contexts/Config";
|
||||
|
||||
const providers = [
|
||||
{id: "ookla", name: "Ookla", image: OoklaImage},
|
||||
{id: "libre", name: "LibreSpeed", image: LibreImage},
|
||||
{id: "cloudflare", name: "Cloudflare", image: CloudflareImage}
|
||||
]
|
||||
|
||||
|
||||
export const Dialog = () => {
|
||||
const close = useContext(DialogContext);
|
||||
const [config, reloadConfig] = useContext(ConfigContext);
|
||||
const [provider, setProvider] = useState(config.provider || "ookla");
|
||||
|
||||
const [licenseAccepted, setLicenseAccepted] = useState(false);
|
||||
const [licenseError, setLicenseError] = useState(false);
|
||||
|
||||
const [ooklaServers, setOoklaServers] = useState({});
|
||||
const [libreServers, setLibreServers] = useState({});
|
||||
|
||||
const [serverId, setServerId] = useState("none");
|
||||
|
||||
useEffect(() => {
|
||||
jsonRequest("/info/server/ookla").then((response) => {
|
||||
setOoklaServers(response);
|
||||
});
|
||||
jsonRequest("/info/server/libre").then((response) => {
|
||||
setLibreServers(response);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (config[provider + "Id"]) setServerId(config[provider + "Id"]);
|
||||
}, [provider]);
|
||||
|
||||
useEffect(() => {
|
||||
if (serverId === "") setServerId("none");
|
||||
}, [serverId]);
|
||||
|
||||
const update = async () => {
|
||||
if (provider === "ookla" && !licenseAccepted) {
|
||||
setLicenseError(true);
|
||||
return;
|
||||
}
|
||||
|
||||
await patchRequest("/config/provider", {value: provider});
|
||||
|
||||
if (serverId !== config[provider + "Id"] && provider !== "cloudflare") {
|
||||
await patchRequest("/config/" + provider + "Id", {value: serverId});
|
||||
}
|
||||
|
||||
reloadConfig();
|
||||
|
||||
close();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="dialog-header">
|
||||
<h4 className="dialog-text">{t("update.provider_title")}</h4>
|
||||
<FontAwesomeIcon icon={faClose} className="dialog-text dialog-icon" onClick={() => close()}/>
|
||||
</div>
|
||||
<div className="provider-dialog-content">
|
||||
<div className="provider-header">
|
||||
{providers.map((current, index) => (
|
||||
<div className={`provider-item ${current.id === provider ? "provider-item-active" : ""}`}
|
||||
key={index} onClick={() => setProvider(current.id)}>
|
||||
<img src={current.image} alt={current.name}/>
|
||||
<h3>{current.name}</h3>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{provider !== "cloudflare" && <div className="provider-content">
|
||||
<div className="provider-setting">
|
||||
<h3>{t("dialog.provider.server")}</h3>
|
||||
<select className="dialog-input provider-input" value={serverId}
|
||||
onChange={(e) => setServerId(e.target.value)}>
|
||||
<option value="none">{t("dialog.provider.choose_automatically")}</option>
|
||||
{provider === "ookla" && Object.keys(ooklaServers).map((current, index) => (
|
||||
<option key={index} value={current}>{ooklaServers[current]}</option>
|
||||
))}
|
||||
{provider === "libre" && Object.keys(libreServers).map((current, index) => (
|
||||
<option key={index} value={current}>{libreServers[current]}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="provider-setting">
|
||||
<h3>{t("dialog.provider.server_id")}</h3>
|
||||
<input type="text" className="dialog-input provider-input" value={serverId === "none" ? "" : serverId}
|
||||
onChange={(e) => setServerId(e.target.value)}/>
|
||||
</div>
|
||||
</div>}
|
||||
{provider === "cloudflare" && <div className="provider-content">
|
||||
<p className="cloudflare-provider-info">{t("dialog.provider.cloudflare_note")}</p>
|
||||
</div>}
|
||||
</div>
|
||||
<div className="provider-dialog-footer">
|
||||
<div className="provider-license-box">
|
||||
{provider === "ookla" && <>
|
||||
<input type="checkbox" className={licenseError ? "cb-error" : ""} id="license" name="license"
|
||||
onChange={(e) => setLicenseAccepted(e.target.checked)}/>
|
||||
<label htmlFor="license"
|
||||
><Trans components={{
|
||||
Eula: <a href="https://www.speedtest.net/about/eula" target="_blank"
|
||||
rel="noreferrer" />,
|
||||
GDPR: <a href="https://www.speedtest.net/about/privacy" target="_blank"
|
||||
rel="noreferrer" />,
|
||||
TOS: <a href="https://www.speedtest.net/about/terms" target="_blank"
|
||||
rel="noreferrer" />}}>dialog.provider.ookla_license</Trans></label>
|
||||
</>}
|
||||
</div>
|
||||
|
||||
<button className="dialog-btn" onClick={update}>{t("dialog.update")}</button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const ProviderDialog = (props) => {
|
||||
return (
|
||||
<>
|
||||
<DialogProvider close={props.onClose}>
|
||||
<Dialog/>
|
||||
</DialogProvider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
1
client/src/common/components/ProviderDialog/index.js
Normal file
1
client/src/common/components/ProviderDialog/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export {ProviderDialog as default} from "./ProviderDialog";
|
||||
95
client/src/common/components/ProviderDialog/styles.sass
Normal file
95
client/src/common/components/ProviderDialog/styles.sass
Normal file
@@ -0,0 +1,95 @@
|
||||
@import "@/common/styles/colors"
|
||||
|
||||
.provider-dialog-content
|
||||
display: flex
|
||||
margin: 1rem 0.5rem
|
||||
user-select: none
|
||||
flex-direction: column
|
||||
|
||||
.provider-header
|
||||
display: flex
|
||||
gap: 1rem
|
||||
|
||||
.provider-item
|
||||
display: flex
|
||||
align-items: center
|
||||
padding: 0.3rem 0.5rem
|
||||
gap: 0.5rem
|
||||
border-radius: 0.8rem
|
||||
border: 2px solid $light-gray
|
||||
color: $darker-white
|
||||
cursor: pointer
|
||||
|
||||
img
|
||||
width: 2.5rem
|
||||
height: 2.5rem
|
||||
|
||||
h3
|
||||
margin: 0
|
||||
|
||||
&:hover
|
||||
background-color: $darker-gray
|
||||
|
||||
.provider-item-active
|
||||
background-color: $light-gray
|
||||
|
||||
&:hover
|
||||
background-color: $light-gray
|
||||
|
||||
.provider-content
|
||||
display: flex
|
||||
flex-direction: column
|
||||
margin-top: 1rem
|
||||
|
||||
|
||||
.provider-setting
|
||||
display: flex
|
||||
gap: 1rem
|
||||
align-items: center
|
||||
justify-content: space-between
|
||||
|
||||
.provider-input
|
||||
width: 20rem
|
||||
box-sizing: border-box
|
||||
margin-top: 0.5rem
|
||||
margin-bottom: 0.5rem
|
||||
font-size: 1.3rem
|
||||
|
||||
h3
|
||||
color: $darker-white
|
||||
|
||||
.cloudflare-provider-info
|
||||
color: $subtext
|
||||
text-align: center
|
||||
|
||||
.provider-dialog-footer
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: space-between
|
||||
|
||||
.provider-license-box
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: 0.5rem
|
||||
|
||||
input
|
||||
border: 2px solid $light-gray
|
||||
|
||||
.cb-error
|
||||
border-color: $red
|
||||
|
||||
label
|
||||
color: $subtext
|
||||
max-width: 16rem
|
||||
flex: 1
|
||||
|
||||
|
||||
@media screen and (max-width: 610px)
|
||||
.provider-dialog-content
|
||||
.provider-header
|
||||
flex-direction: column
|
||||
|
||||
@media screen and (max-width: 520px)
|
||||
.provider-dialog-content
|
||||
.provider-setting .provider-input
|
||||
width: 60%
|
||||
@@ -1,14 +1,13 @@
|
||||
import React, {createContext, useContext, useEffect, useState} from "react";
|
||||
import {InputDialogContext} from "../InputDialog";
|
||||
import {request} from "@/common/utils/RequestUtil";
|
||||
import {acceptDialog, apiErrorDialog, passwordRequiredDialog} from "@/common/contexts/Config/dialog";
|
||||
import {apiErrorDialog, passwordRequiredDialog} from "@/common/contexts/Config/dialog";
|
||||
|
||||
export const ConfigContext = createContext({});
|
||||
|
||||
export const ConfigProvider = (props) => {
|
||||
const [config, setConfig] = useState({});
|
||||
const [setDialog] = useContext(InputDialogContext);
|
||||
const [dialogShown, setDialogShown] = useState(false);
|
||||
|
||||
const reloadConfig = () => {
|
||||
request("/config").then(async res => {
|
||||
@@ -32,13 +31,6 @@ export const ConfigProvider = (props) => {
|
||||
|
||||
const checkConfig = async () => (await request("/config")).json();
|
||||
|
||||
useEffect(() => {
|
||||
if (config.acceptOoklaLicense !== undefined && config.acceptOoklaLicense === "false" && !dialogShown) {
|
||||
setDialogShown(true);
|
||||
setDialog(acceptDialog());
|
||||
}
|
||||
}, [config]);
|
||||
|
||||
useEffect(reloadConfig, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import {patchRequest} from "@/common/utils/RequestUtil";
|
||||
import {t} from "i18next";
|
||||
import {Trans} from "react-i18next";
|
||||
|
||||
const OOKLA_ABOUT_URL = "https://www.speedtest.net/about";
|
||||
const OOKLA_TERMS_URL = OOKLA_ABOUT_URL + "/terms";
|
||||
const OOKLA_EULA_URL = OOKLA_ABOUT_URL + "/eula";
|
||||
const OOKLA_PRIVACY_URL = OOKLA_ABOUT_URL + "/privacy";
|
||||
|
||||
export const passwordRequiredDialog = () => ({
|
||||
title: t("dialog.password.title"),
|
||||
@@ -26,13 +19,4 @@ export const apiErrorDialog = () => ({
|
||||
buttonText: t("dialog.retry"),
|
||||
disableCloseButton: true,
|
||||
onSuccess: () => window.location.reload()
|
||||
});
|
||||
|
||||
export const acceptDialog = () => ({
|
||||
title: t("dialog.accept.title"),
|
||||
description: <Trans components={{Bold: <span className="dialog-value"/>, EULA: <a href={OOKLA_EULA_URL} target="_blank"/>,
|
||||
Privacy: <a href={OOKLA_PRIVACY_URL} target="_blank"/>, Terms: <a href={OOKLA_TERMS_URL} target="_blank"/> }}>dialog.accept.description</Trans>,
|
||||
buttonText: t("dialog.accept.button"),
|
||||
disableCloseButton: true,
|
||||
onSuccess: () => patchRequest("/config/acceptOoklaLicense", {value: true})
|
||||
});
|
||||
258
package-lock.json
generated
258
package-lock.json
generated
@@ -8,6 +8,7 @@
|
||||
"name": "myspeed",
|
||||
"version": "1.0.8",
|
||||
"dependencies": {
|
||||
"@cloudflare/speedtest": "^1.3.0",
|
||||
"axios": "^1.6.8",
|
||||
"bcrypt": "^5.1.1",
|
||||
"cron-validator": "^1.3.1",
|
||||
@@ -38,6 +39,19 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudflare/speedtest": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/speedtest/-/speedtest-1.3.0.tgz",
|
||||
"integrity": "sha512-/uXLCVbKcdj/ueD7/StCO/+RC/aTfHo9pBDO9GSD8kRl7oaIdMs9xC4QkPM8EvYGa3OrbVQLXTp/PLNkwt3gNg==",
|
||||
"dependencies": {
|
||||
"d3-scale": "^4.0.2",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"lodash.memoize": "^4.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@gar/promisify": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
||||
@@ -94,25 +108,6 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/node-fetch": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/nopt": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
|
||||
@@ -811,6 +806,81 @@
|
||||
"resolved": "https://registry.npmjs.org/cron-validator/-/cron-validator-1.3.1.tgz",
|
||||
"integrity": "sha512-C1HsxuPCY/5opR55G5/WNzyEGDWFVG+6GLrA+fW/sCTcP6A6NTjUP2AK7B8n2PyFs90kDG2qzwm8LMheADku6A=="
|
||||
},
|
||||
"node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||
"dependencies": {
|
||||
"internmap": "1 - 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-format": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
|
||||
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-interpolate": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-scale": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||
"dependencies": {
|
||||
"d3-array": "2.10.0 - 3",
|
||||
"d3-format": "1 - 3",
|
||||
"d3-interpolate": "1.2.0 - 3",
|
||||
"d3-time": "2.1.1 - 3",
|
||||
"d3-time-format": "2 - 4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-time": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
|
||||
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
||||
"dependencies": {
|
||||
"d3-array": "2 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-time-format": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
|
||||
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
|
||||
"dependencies": {
|
||||
"d3-time": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "2.30.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
|
||||
@@ -1669,6 +1739,14 @@
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
|
||||
},
|
||||
"node_modules/internmap": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
||||
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/ip": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
|
||||
@@ -1768,11 +1846,25 @@
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/isomorphic-fetch": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz",
|
||||
"integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==",
|
||||
"dependencies": {
|
||||
"node-fetch": "^2.6.1",
|
||||
"whatwg-fetch": "^3.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
"integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="
|
||||
},
|
||||
"node_modules/long": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
|
||||
@@ -2145,6 +2237,25 @@
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz",
|
||||
"integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA=="
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-gyp": {
|
||||
"version": "8.4.1",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz",
|
||||
@@ -3462,6 +3573,11 @@
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
|
||||
},
|
||||
"node_modules/whatwg-fetch": {
|
||||
"version": "3.6.20",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
|
||||
"integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg=="
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
@@ -3593,6 +3709,16 @@
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
}
|
||||
},
|
||||
"@cloudflare/speedtest": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/speedtest/-/speedtest-1.3.0.tgz",
|
||||
"integrity": "sha512-/uXLCVbKcdj/ueD7/StCO/+RC/aTfHo9pBDO9GSD8kRl7oaIdMs9xC4QkPM8EvYGa3OrbVQLXTp/PLNkwt3gNg==",
|
||||
"requires": {
|
||||
"d3-scale": "^4.0.2",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"lodash.memoize": "^4.1.2"
|
||||
}
|
||||
},
|
||||
"@gar/promisify": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
||||
@@ -3640,14 +3766,6 @@
|
||||
"wide-align": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"requires": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"nopt": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
|
||||
@@ -4181,6 +4299,60 @@
|
||||
"resolved": "https://registry.npmjs.org/cron-validator/-/cron-validator-1.3.1.tgz",
|
||||
"integrity": "sha512-C1HsxuPCY/5opR55G5/WNzyEGDWFVG+6GLrA+fW/sCTcP6A6NTjUP2AK7B8n2PyFs90kDG2qzwm8LMheADku6A=="
|
||||
},
|
||||
"d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||
"requires": {
|
||||
"internmap": "1 - 2"
|
||||
}
|
||||
},
|
||||
"d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="
|
||||
},
|
||||
"d3-format": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
|
||||
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA=="
|
||||
},
|
||||
"d3-interpolate": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||
"requires": {
|
||||
"d3-color": "1 - 3"
|
||||
}
|
||||
},
|
||||
"d3-scale": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||
"requires": {
|
||||
"d3-array": "2.10.0 - 3",
|
||||
"d3-format": "1 - 3",
|
||||
"d3-interpolate": "1.2.0 - 3",
|
||||
"d3-time": "2.1.1 - 3",
|
||||
"d3-time-format": "2 - 4"
|
||||
}
|
||||
},
|
||||
"d3-time": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
|
||||
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
||||
"requires": {
|
||||
"d3-array": "2 - 3"
|
||||
}
|
||||
},
|
||||
"d3-time-format": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
|
||||
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
|
||||
"requires": {
|
||||
"d3-time": "1 - 3"
|
||||
}
|
||||
},
|
||||
"date-fns": {
|
||||
"version": "2.30.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
|
||||
@@ -4820,6 +4992,11 @@
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
|
||||
},
|
||||
"internmap": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
||||
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="
|
||||
},
|
||||
"ip": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
|
||||
@@ -4898,11 +5075,25 @@
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"optional": true
|
||||
},
|
||||
"isomorphic-fetch": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz",
|
||||
"integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==",
|
||||
"requires": {
|
||||
"node-fetch": "^2.6.1",
|
||||
"whatwg-fetch": "^3.4.1"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
"integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="
|
||||
},
|
||||
"long": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
|
||||
@@ -5177,6 +5368,14 @@
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz",
|
||||
"integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"requires": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node-gyp": {
|
||||
"version": "8.4.1",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz",
|
||||
@@ -6146,6 +6345,11 @@
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
|
||||
},
|
||||
"whatwg-fetch": {
|
||||
"version": "3.6.20",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
|
||||
"integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg=="
|
||||
},
|
||||
"whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"dev": "concurrently --kill-others-on-fail \"npm run server\" \"npm run client\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@cloudflare/speedtest": "^1.3.0",
|
||||
"axios": "^1.6.8",
|
||||
"bcrypt": "^5.1.1",
|
||||
"cron-validator": "^1.3.1",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports.version = "1.2.0";
|
||||
module.exports.list = [
|
||||
module.exports.ooklaVersion = "1.2.0";
|
||||
module.exports.ooklaList = [
|
||||
// MacOS
|
||||
{os: 'darwin', arch: 'x64', suffix: 'macosx-x86_64.tgz'},
|
||||
|
||||
@@ -14,4 +14,28 @@ module.exports.list = [
|
||||
|
||||
// FreeBSD
|
||||
{os: 'freebsd', arch: 'x64', suffix: 'freebsd12-x86_64.pkg'}
|
||||
];
|
||||
|
||||
module.exports.libreVersion = "1.0.10";
|
||||
module.exports.libreList = [
|
||||
// MacOS
|
||||
{os: 'darwin', arch: 'x64', suffix: 'darwin_amd64.tar.gz'},
|
||||
{os: 'darwin', arch: 'arm64', suffix: 'darwin_arm64.tar.gz'},
|
||||
|
||||
// Windows
|
||||
{os: 'win32', arch: 'x64', suffix: 'windows_amd64.zip'},
|
||||
{os: 'win32', arch: 'ia32', suffix: 'windows_386.zip'},
|
||||
{os: 'win32', arch: 'arm64', suffix: 'windows_arm64.zip'},
|
||||
|
||||
// Linux
|
||||
{os: 'linux', arch: 'x64', suffix: 'linux_amd64.tar.gz'},
|
||||
{os: 'linux', arch: 'ia32', suffix: 'linux_386.tar.gz'},
|
||||
{os: 'linux', arch: 'arm', suffix: 'linux_armv7.tar.gz'},
|
||||
{os: 'linux', arch: 'arm64', suffix: 'linux_arm64.tar.gz'},
|
||||
|
||||
// FreeBSD
|
||||
{os: 'freebsd', arch: 'x64', suffix: 'freebsd_amd64.tar.gz'},
|
||||
{os: 'freebsd', arch: 'ia32', suffix: 'freebsd_386.tar.gz'},
|
||||
{os: 'freebsd', arch: 'arm', suffix: 'freebsd_armv7.tar.gz'},
|
||||
{os: 'freebsd', arch: 'arm64', suffix: 'freebsd_arm64.tar.gz'}
|
||||
]
|
||||
@@ -6,10 +6,11 @@ const configDefaults = {
|
||||
download: "100",
|
||||
upload: "50",
|
||||
cron: "0 * * * *",
|
||||
serverId: "none",
|
||||
provider: "none",
|
||||
ooklaId: "none",
|
||||
libreId: "none",
|
||||
password: "none",
|
||||
passwordLevel: "none",
|
||||
acceptOoklaLicense: "false"
|
||||
passwordLevel: "none"
|
||||
}
|
||||
|
||||
module.exports.insertDefaults = async () => {
|
||||
@@ -27,8 +28,7 @@ module.exports.listAll = async () => {
|
||||
}
|
||||
|
||||
module.exports.getValue = async (key) => {
|
||||
if (process.env.PREVIEW_MODE === "true" && key === "acceptOoklaLicense") return true;
|
||||
return (await config.findByPk(key)).value;
|
||||
return (await config.findByPk(key))?.value;
|
||||
}
|
||||
|
||||
module.exports.updateValue = async (key, newValue) => {
|
||||
|
||||
34
server/controller/servers.js
Normal file
34
server/controller/servers.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const fs = require("fs");
|
||||
let ooklaServers;
|
||||
let libreServers;
|
||||
|
||||
module.exports.getLibreServers = () => {
|
||||
if (libreServers) return libreServers;
|
||||
|
||||
if (fs.existsSync("./data/servers/librespeed.json")) {
|
||||
libreServers = fs.readFileSync("./data/servers/librespeed.json");
|
||||
libreServers = JSON.parse(libreServers);
|
||||
|
||||
return libreServers;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
module.exports.getOoklaServers = () => {
|
||||
if (ooklaServers) return ooklaServers;
|
||||
|
||||
if (fs.existsSync("./data/servers/ookla.json")) {
|
||||
ooklaServers = fs.readFileSync("./data/servers/ookla.json");
|
||||
ooklaServers = JSON.parse(ooklaServers);
|
||||
|
||||
return ooklaServers;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
module.exports.getByMode = (mode) => {
|
||||
if (mode === "ookla") return this.getOoklaServers();
|
||||
if (mode === "libre") return this.getLibreServers();
|
||||
}
|
||||
@@ -2,8 +2,8 @@ const tests = require('../models/Speedtests');
|
||||
const {Op, Sequelize} = require("sequelize");
|
||||
const {mapFixed, mapRounded, calculateTestAverages} = require("../util/helpers");
|
||||
|
||||
module.exports.create = async (ping, download, upload, time, type = "auto", error = null) => {
|
||||
return (await tests.create({ping, download, upload, error, type, time})).id;
|
||||
module.exports.create = async (ping, download, upload, time, serverId, type = "auto", error = null) => {
|
||||
return (await tests.create({ping, download, upload, error, serverId, type, time})).id;
|
||||
}
|
||||
|
||||
module.exports.getOne = async (id) => {
|
||||
|
||||
@@ -59,8 +59,8 @@ const run = async () => {
|
||||
}
|
||||
|
||||
db.authenticate().then(() => {
|
||||
console.log("Successfully connected to the database file");
|
||||
run().then(undefined);
|
||||
console.log("Successfully connected to the database " + (process.env.DB_TYPE === "mysql" ? "server" : "file"));
|
||||
run().then(undefined);
|
||||
}).catch(err => {
|
||||
console.error("Could not open the database file. Maybe it is damaged?: " + err.message);
|
||||
process.exit(111);
|
||||
|
||||
@@ -7,6 +7,10 @@ module.exports = db.define("speedtests", {
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
serverId: {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 0
|
||||
},
|
||||
ping: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false
|
||||
|
||||
@@ -7,10 +7,8 @@ const password = require('../middlewares/password');
|
||||
app.get("/", password(true), async (req, res) => {
|
||||
let configValues = {};
|
||||
(await config.listAll()).forEach(row => {
|
||||
if (row.key !== "password" && !(req.viewMode && ["serverId", "cron", "passwordLevel"].includes(row.key)))
|
||||
if (row.key !== "password" && !(req.viewMode && ["ooklaId", "libreId", "cron", "passwordLevel"].includes(row.key)))
|
||||
configValues[row.key] = row.value;
|
||||
if (process.env.PREVIEW_MODE === "true" && row.key === "acceptOoklaLicense")
|
||||
configValues[row.key] = true;
|
||||
});
|
||||
configValues['viewMode'] = req.viewMode;
|
||||
configValues['previewMode'] = process.env.PREVIEW_MODE === "true";
|
||||
@@ -28,11 +26,15 @@ app.patch("/:key", password(false), async (req, res) => {
|
||||
if ((req.params.key === "ping" || req.params.key === "download" || req.params.key === "upload") && isNaN(req.body.value))
|
||||
return res.status(400).json({message: "You need to provide a number in order to change this"});
|
||||
|
||||
if ((req.params.key === "ooklaId" || req.params.key === "libreId") && (isNaN(req.body.value) && req.body.value !== "none"))
|
||||
return res.status(400).json({message: "You need to provide a number in order to change this"});
|
||||
|
||||
|
||||
if (req.params.key === "passwordLevel" && !["none", "read"].includes(req.body.value))
|
||||
return res.status(400).json({message: "You need to provide either none or read-access"});
|
||||
|
||||
if (req.params.key === "acceptOoklaLicense" && typeof req.body.value !== "boolean")
|
||||
return res.status(400).json({message: "You need to provide a boolean value"});
|
||||
if (req.params.key === "provider" && !["ookla", "libre", "cloudflare"].includes(req.body.value))
|
||||
return res.status(400).json({message: "You need to provide a valid provider"});
|
||||
|
||||
if (req.params.key === "ping")
|
||||
req.body.value = req.body.value.toString().split(".")[0];
|
||||
@@ -45,9 +47,6 @@ app.patch("/:key", password(false), async (req, res) => {
|
||||
if (!await config.updateValue(req.params.key, req.body.value.toString()))
|
||||
return res.status(404).json({message: "The provided key does not exist"});
|
||||
|
||||
if (process.env.PREVIEW_MODE === "true" && req.params.key === "acceptOoklaLicense")
|
||||
return res.status(403).json({message: "You can't change the Ookla license acceptance in preview mode"});
|
||||
|
||||
if (process.env.PREVIEW_MODE === "true" && (req.params.key === "password" || req.params.key === "passwordLevel"))
|
||||
return res.status(403).json({message: "You can't change the password in preview mode"});
|
||||
|
||||
|
||||
@@ -22,8 +22,7 @@ app.get("/statistics", password(true), async (req, res) => {
|
||||
|
||||
app.post("/run", password(false), async (req, res) => {
|
||||
if (pauseController.currentState) return res.status(410).json({message: "The speedtests are currently paused"});
|
||||
if (await config.getValue("acceptOoklaLicense") === "false")
|
||||
return res.status(410).json({message: "You need to accept the ookla license first"});
|
||||
if (await config.getValue("provider") === "none") return res.status(410).json({message: "No provider selected"});
|
||||
let speedtest = await testTask.create("custom");
|
||||
if (speedtest !== undefined) return res.status(409).json({message: "An speedtest is already running"});
|
||||
res.json({message: "Speedtest successfully created"});
|
||||
|
||||
@@ -2,10 +2,8 @@ const app = require('express').Router();
|
||||
const version = require('../../package.json').version;
|
||||
const remote_url = "https://api.github.com/repos/gnmyt/myspeed/releases/latest";
|
||||
const axios = require('axios');
|
||||
const fs = require("fs");
|
||||
const password = require('../middlewares/password');
|
||||
|
||||
let servers;
|
||||
const serverController = require('../controller/servers');
|
||||
|
||||
app.get("/version", password(false), async (req, res) => {
|
||||
if (process.env.PREVIEW_MODE === "true") return res.json({local: version, remote: "0"});
|
||||
@@ -17,15 +15,11 @@ app.get("/version", password(false), async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/server", password(false), (req, res) => {
|
||||
if (servers) return res.json(JSON.parse(servers));
|
||||
app.get("/server/:provider", password(false), (req, res) => {
|
||||
if (!["ookla", "libre"].includes(req.params.provider))
|
||||
return res.status(400).json({message: "Invalid provider"});
|
||||
|
||||
if (fs.existsSync("./data/servers.json")) {
|
||||
servers = fs.readFileSync("./data/servers.json");
|
||||
return res.json(JSON.parse(servers));
|
||||
} else {
|
||||
return res.json([]);
|
||||
}
|
||||
res.json(serverController.getByMode(req.params.provider));
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
@@ -2,14 +2,12 @@ const speedTest = require('../util/speedtest');
|
||||
const tests = require('../controller/speedtests');
|
||||
const config = require('../controller/config');
|
||||
const controller = require("../controller/recommendations");
|
||||
const parseData = require('../util/providers/parseData');
|
||||
let {setState, sendRunning, sendError, sendFinished} = require("./integrations");
|
||||
const serverController = require("../controller/servers");
|
||||
|
||||
let isRunning = false;
|
||||
|
||||
const roundSpeed = (bytes, elapsed) => {
|
||||
return Math.round((bytes * 8 / elapsed) / 10) / 100;
|
||||
}
|
||||
|
||||
const setRunning = (running, sendRequest = true) => {
|
||||
isRunning = running;
|
||||
|
||||
@@ -35,25 +33,70 @@ const createRecommendations = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.executeCloudflare = async () => {
|
||||
try {
|
||||
const {default: SpeedTest} = await import('@cloudflare/speedtest');
|
||||
|
||||
// This needs to be disabled because of a library issue
|
||||
// See https://github.com/cloudflare/speedtest/issues/17
|
||||
console.warn = () => {};
|
||||
|
||||
const startTime = new Date().getTime();
|
||||
return await new Promise(resolve => {
|
||||
const speedTest = new SpeedTest();
|
||||
speedTest.onFinish = results => {
|
||||
resolve({...results.getSummary(), elapsed: new Date().getTime() - startTime});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error loading SpeedTest module:', error);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.run = async (retryAuto = false) => {
|
||||
setRunning(true);
|
||||
let serverId = await config.getValue("serverId");
|
||||
let mode = await config.getValue("provider");
|
||||
|
||||
if (mode === "none") {
|
||||
setRunning(false);
|
||||
throw {message: "No provider selected"};
|
||||
}
|
||||
|
||||
let serverId = mode === "cloudflare" ? 0 : await config.getValue(mode + "Id");
|
||||
|
||||
if (serverId === "none")
|
||||
serverId = undefined;
|
||||
|
||||
let speedtest = await (retryAuto ? speedTest() : speedTest(serverId));
|
||||
let speedtest;
|
||||
if (mode === "cloudflare") {
|
||||
speedtest = await this.executeCloudflare();
|
||||
} else {
|
||||
speedtest = await (retryAuto ? speedTest(mode) : speedTest(mode, serverId));
|
||||
}
|
||||
|
||||
if (serverId === undefined)
|
||||
await config.updateValue("serverId", speedtest.server.id);
|
||||
if (mode === "ookla" && speedtest.server) {
|
||||
if (serverId === undefined) await config.updateValue("ooklaId", speedtest.server?.id);
|
||||
serverId = speedtest.server?.id;
|
||||
}
|
||||
|
||||
if (Object.keys(speedtest).length === 0) throw {message: "No response, even after trying again, test timed out."};
|
||||
if (mode === "libre" && speedtest.server) {
|
||||
let server = Object.entries(serverController.getLibreServers())
|
||||
.filter(([, value]) => value === speedtest.server.name)[0][0];
|
||||
|
||||
return speedtest;
|
||||
if (server) {
|
||||
if (serverId === undefined) await config.updateValue("libreId", server);
|
||||
serverId = parseInt(server);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(speedtest).length <= 1) throw {message: "No response, even after trying again, test timed out."};
|
||||
|
||||
return {...speedtest, serverId}
|
||||
}
|
||||
|
||||
module.exports.create = async (type = "auto", retried = false) => {
|
||||
if (await config.getValue("acceptOoklaLicense") === 'false') return;
|
||||
const mode = await config.getValue("provider");
|
||||
if (mode === "none") return 400;
|
||||
if (isRunning && !retried) return 500;
|
||||
|
||||
try {
|
||||
@@ -69,18 +112,16 @@ module.exports.create = async (type = "auto", retried = false) => {
|
||||
test = await this.run(retried);
|
||||
}
|
||||
|
||||
let ping = Math.round(test.ping.latency);
|
||||
let download = roundSpeed(test.download.bytes, test.download.elapsed);
|
||||
let upload = roundSpeed(test.upload.bytes, test.upload.elapsed);
|
||||
let time = Math.round((test.download.elapsed + test.upload.elapsed) / 1000);
|
||||
let testResult = await tests.create(ping, download, upload, time, type);
|
||||
let {ping, download, upload, time} = await parseData.parseData(mode, test);
|
||||
|
||||
let testResult = await tests.create(ping, download, upload, time, test.serverId, type);
|
||||
console.log(`Test #${testResult} was executed successfully in ${time}s. 🏓 ${ping} ⬇ ${download}️ ⬆ ${upload}️`);
|
||||
createRecommendations().then(() => "");
|
||||
setRunning(false);
|
||||
sendFinished({ping, download, upload, time}).then(() => "");
|
||||
} catch (e) {
|
||||
if (!retried) return this.create(type, true);
|
||||
let testResult = await tests.create(-1, -1, -1, null, type, e.message);
|
||||
let testResult = await tests.create(-1, -1, -1, null, 0, type, e.message);
|
||||
await sendError(e.message);
|
||||
setRunning(false, false);
|
||||
console.log(`Test #${testResult} was not executed successfully. Please try reconnecting to the internet or restarting the software: ` + e.message);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const neededFolder = ["data", "bin", "data/logs"];
|
||||
const neededFolder = ["data", "bin", "data/logs", "data/servers"];
|
||||
|
||||
neededFolder.forEach(folder => {
|
||||
if (!fs.existsSync(folder)) {
|
||||
|
||||
@@ -1,45 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const {get} = require('https');
|
||||
const decompress = require("decompress");
|
||||
const {file} = require("tmp");
|
||||
const decompressTarGz = require('decompress-targz');
|
||||
const decompressUnzip = require('decompress-unzip');
|
||||
const binaries = require('../config/binaries');
|
||||
|
||||
const binaryRegex = /speedtest(.exe)?$/;
|
||||
const binaryDirectory = __dirname + "/../../bin/";
|
||||
const binaryPath = `${binaryDirectory}/speedtest` + (process.platform === "win32" ? ".exe" : "");
|
||||
|
||||
const downloadPath = `https://install.speedtest.net/app/cli/ookla-speedtest-${binaries.version}-`;
|
||||
|
||||
module.exports.fileExists = async () => fs.existsSync(binaryPath);
|
||||
|
||||
module.exports.downloadFile = async () => {
|
||||
const binary = binaries.list.find(b => b.os === process.platform && b.arch === process.arch);
|
||||
|
||||
if (!binary)
|
||||
throw new Error(`Your platform (${process.platform}-${process.arch}) is not supported by the Speedtest CLI`);
|
||||
|
||||
await new Promise((resolve) => {
|
||||
file({postfix: binary.suffix}, async (err, path) => {
|
||||
get(downloadPath + binary.suffix, async resp => {
|
||||
resp.pipe(fs.createWriteStream(path)).on('finish', async () => {
|
||||
await decompress(path, binaryDirectory, {
|
||||
plugins: [decompressTarGz(), decompressUnzip()],
|
||||
filter: file => binaryRegex.test(file.path),
|
||||
map: file => {
|
||||
file.path = "speedtest" + (process.platform === "win32" ? ".exe" : "");
|
||||
return file;
|
||||
}
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
const libreProvider = require('./providers/loadLibre');
|
||||
const ooklaProvider = require('./providers/loadOokla');
|
||||
|
||||
module.exports.load = async () => {
|
||||
if (!await this.fileExists())
|
||||
await this.downloadFile();
|
||||
await libreProvider.load();
|
||||
await ooklaProvider.load();
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
const axios = require('axios');
|
||||
const fs = require('fs');
|
||||
|
||||
if (!fs.existsSync("data/servers.json")) {
|
||||
// Load servers from ookla
|
||||
if (!fs.existsSync("data/servers/ookla.json")) {
|
||||
let servers = {};
|
||||
try {
|
||||
axios.get("https://www.speedtest.net/api/js/servers?limit=20")
|
||||
@@ -12,13 +13,33 @@ if (!fs.existsSync("data/servers.json")) {
|
||||
});
|
||||
|
||||
try {
|
||||
fs.writeFileSync("data/servers.json", JSON.stringify(servers, null, 4));
|
||||
fs.writeFileSync("data/servers/ookla.json", JSON.stringify(servers, null, 4));
|
||||
} catch (e) {
|
||||
console.error("Could not save servers file")
|
||||
console.error("Could not save servers file");
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Could not get servers");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Load servers from librespeed
|
||||
if (!fs.existsSync("data/servers/librespeed.json")) {
|
||||
let servers = {};
|
||||
try {
|
||||
axios.get("https://librespeed.org/backend-servers/servers.php")
|
||||
.then(res => res.data)
|
||||
.then(data => {
|
||||
data?.forEach(row => {
|
||||
servers[row.id] = row.name;
|
||||
});
|
||||
try {
|
||||
fs.writeFileSync("data/servers/librespeed.json", JSON.stringify(servers, null, 4));
|
||||
} catch (e) {
|
||||
console.error("Could not save servers file");
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Could not get servers");
|
||||
}
|
||||
}
|
||||
49
server/util/providers/loadLibre.js
Normal file
49
server/util/providers/loadLibre.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const fs = require('fs');
|
||||
const {get} = require('https');
|
||||
const decompress = require("decompress");
|
||||
const {file} = require("tmp");
|
||||
const decompressTarGz = require('decompress-targz');
|
||||
const decompressUnzip = require('decompress-unzip');
|
||||
const binaries = require('../../config/binaries');
|
||||
|
||||
const binaryRegex = /librespeed-cli(.exe)?$/;
|
||||
const binaryDirectory = __dirname + "/../../../bin/";
|
||||
const binaryPath = `${binaryDirectory}/librespeed-cli` + (process.platform === "win32" ? ".exe" : "");
|
||||
|
||||
const downloadPath = `https://github.com/librespeed/speedtest-cli/releases/download/v${binaries.libreVersion}/librespeed-cli_${binaries.libreVersion}_`;
|
||||
|
||||
module.exports.fileExists = async () => fs.existsSync(binaryPath);
|
||||
|
||||
module.exports.downloadFile = async () => {
|
||||
const binary = binaries.libreList.find(b => b.os === process.platform && b.arch === process.arch);
|
||||
|
||||
if (!binary)
|
||||
throw new Error(`Your platform (${process.platform}-${process.arch}) is not supported by the LibreSpeed CLI`);
|
||||
|
||||
await new Promise((resolve) => {
|
||||
file({postfix: binary.suffix}, async (err, path) => {
|
||||
const location = await new Promise((resolve) => get(downloadPath + binary.suffix, (res) => {
|
||||
resolve(res.headers.location);
|
||||
}));
|
||||
|
||||
get(location, async resp => {
|
||||
resp.pipe(fs.createWriteStream(path)).on('finish', async () => {
|
||||
await decompress(path, binaryDirectory, {
|
||||
plugins: [decompressTarGz(), decompressUnzip()],
|
||||
filter: file => binaryRegex.test(file.path),
|
||||
map: file => {
|
||||
file.path = "librespeed-cli" + (process.platform === "win32" ? ".exe" : "");
|
||||
return file;
|
||||
}
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.load = async () => {
|
||||
if (!await this.fileExists())
|
||||
await this.downloadFile();
|
||||
}
|
||||
45
server/util/providers/loadOokla.js
Normal file
45
server/util/providers/loadOokla.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const fs = require('fs');
|
||||
const {get} = require('https');
|
||||
const decompress = require("decompress");
|
||||
const {file} = require("tmp");
|
||||
const decompressTarGz = require('decompress-targz');
|
||||
const decompressUnzip = require('decompress-unzip');
|
||||
const binaries = require('../../config/binaries');
|
||||
|
||||
const binaryRegex = /speedtest(.exe)?$/;
|
||||
const binaryDirectory = __dirname + "/../../../bin/";
|
||||
const binaryPath = `${binaryDirectory}/ookla` + (process.platform === "win32" ? ".exe" : "");
|
||||
|
||||
const downloadPath = `https://install.speedtest.net/app/cli/ookla-speedtest-${binaries.ooklaVersion}-`;
|
||||
|
||||
module.exports.fileExists = async () => fs.existsSync(binaryPath);
|
||||
|
||||
module.exports.downloadFile = async () => {
|
||||
const binary = binaries.ooklaList.find(b => b.os === process.platform && b.arch === process.arch);
|
||||
|
||||
if (!binary)
|
||||
throw new Error(`Your platform (${process.platform}-${process.arch}) is not supported by the Speedtest CLI`);
|
||||
|
||||
await new Promise((resolve) => {
|
||||
file({postfix: binary.suffix}, async (err, path) => {
|
||||
get(downloadPath + binary.suffix, async resp => {
|
||||
resp.pipe(fs.createWriteStream(path)).on('finish', async () => {
|
||||
await decompress(path, binaryDirectory, {
|
||||
plugins: [decompressTarGz(), decompressUnzip()],
|
||||
filter: file => binaryRegex.test(file.path),
|
||||
map: file => {
|
||||
file.path = "speedtest" + (process.platform === "win32" ? ".exe" : "");
|
||||
return file;
|
||||
}
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.load = async () => {
|
||||
if (!await this.fileExists())
|
||||
await this.downloadFile();
|
||||
}
|
||||
38
server/util/providers/parseData.js
Normal file
38
server/util/providers/parseData.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const roundSpeed = (bytes, elapsed) => {
|
||||
return Math.round((bytes * 8 / elapsed) / 10) / 100;
|
||||
}
|
||||
|
||||
module.exports.parseOokla = (test) => {
|
||||
let ping = Math.round(test.ping.latency);
|
||||
let download = roundSpeed(test.download.bytes, test.download.elapsed);
|
||||
let upload = roundSpeed(test.upload.bytes, test.upload.elapsed);
|
||||
let time = Math.round((test.download.elapsed + test.upload.elapsed) / 1000);
|
||||
|
||||
return {ping, download, upload, time};
|
||||
}
|
||||
|
||||
module.exports.parseLibre = (test) => {
|
||||
return {ping: test.ping, upload: test.upload, download: test.download, time: Math.round(test.elapsed / 1000)};
|
||||
}
|
||||
|
||||
module.exports.parseCloudflare = async (test) => {
|
||||
let ping = Math.round(test.latency);
|
||||
let download = Math.round(test.download / 10000) / 100;
|
||||
let upload = Math.round(test.upload / 10000) / 100;
|
||||
let time = Math.round(test.elapsed / 1000);
|
||||
|
||||
return {ping, download, upload, time};
|
||||
}
|
||||
|
||||
module.exports.parseData = (provider, data) => {
|
||||
switch (provider) {
|
||||
case "ookla":
|
||||
return this.parseOokla(data);
|
||||
case "libre":
|
||||
return this.parseLibre(data);
|
||||
case "cloudflare":
|
||||
return this.parseCloudflare(data);
|
||||
default:
|
||||
throw {message: "Invalid provider"};
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,53 @@
|
||||
const {spawn} = require('child_process');
|
||||
|
||||
module.exports = async (serverId, binary_path = './bin/speedtest' + (process.platform === "win32" ? ".exe" : "")) => {
|
||||
const args = ['--accept-license', '--accept-gdpr', '--format=jsonl'];
|
||||
if (serverId) args.push(`--server-id=${serverId}`);
|
||||
module.exports = async (mode, serverId) => {
|
||||
const binaryPath = mode === "ookla" ? './bin/speedtest' + (process.platform === "win32" ? ".exe" : "")
|
||||
: './bin/librespeed-cli' + (process.platform === "win32" ? ".exe" : "");
|
||||
|
||||
const startTime = new Date().getTime();
|
||||
let args;
|
||||
|
||||
if (mode === "ookla") {
|
||||
args = ['--accept-license', '--accept-gdpr', '--format=json'];
|
||||
if (serverId) args.push(`--server-id=${serverId}`);
|
||||
} else {
|
||||
args = ['--json', '--duration=5'];
|
||||
if (serverId) args.push(`--server=${serverId}`);
|
||||
}
|
||||
|
||||
let result = {};
|
||||
|
||||
const process = spawn(binary_path, args, {windowsHide: true});
|
||||
const testProcess = spawn(binaryPath, args, {windowsHide: true});
|
||||
|
||||
process.stdout.on('data', (buffer) => {
|
||||
testProcess.stderr.on('data', (buffer) => {
|
||||
result.error = buffer.toString();
|
||||
if (buffer.toString().includes("Too many requests")) {
|
||||
result.error = "Too many requests. Please try again later";
|
||||
}
|
||||
});
|
||||
|
||||
testProcess.stdout.on('data', (buffer) => {
|
||||
const line = buffer.toString().replace("\n", "");
|
||||
if (!line.startsWith("{")) return;
|
||||
if (!(line.startsWith("{") || line.startsWith("["))) return;
|
||||
|
||||
let data = {};
|
||||
try {
|
||||
data = JSON.parse(line);
|
||||
if (line.startsWith("[")) data = data[0];
|
||||
} catch (e) {
|
||||
data.error = e.message;
|
||||
}
|
||||
|
||||
if (data.error) result.error = data.error;
|
||||
|
||||
if (data.type === "result") result = data;
|
||||
if ((mode === "ookla" && data.type === "result") || mode === "libre") result = data;
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
process.on('error', e => reject({message: e}));
|
||||
process.on('exit', resolve);
|
||||
testProcess.on('error', e => reject({message: e}));
|
||||
testProcess.on('exit', resolve);
|
||||
});
|
||||
|
||||
if (result.error) throw new Error(result.error);
|
||||
return result;
|
||||
return {...result, elapsed: new Date().getTime() - startTime};
|
||||
}
|
||||
Reference in New Issue
Block a user