diff --git a/Dockerfile b/Dockerfile
index c14a1ca6..ce442c52 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,33 +1,25 @@
-FROM node:22-alpine AS build
-RUN apk add --no-cache g++ make cmake python3 py3-setuptools
+FROM node:20-alpine AS client-build
-WORKDIR /myspeed
-
-COPY ./client ./client
-COPY ./server ./server
-COPY ./package.json ./package.json
-
-RUN yarn install
-RUN cd client && yarn install --force
+WORKDIR /client
+COPY ./client/package*.json ./
+RUN npm install --force
+COPY ./client ./
RUN npm run build
-RUN mv /myspeed/client/build /myspeed
-FROM node:22-alpine
+FROM denoland/deno:alpine
RUN apk add --no-cache tzdata
-ENV NODE_ENV=production
ENV TZ=Etc/UTC
WORKDIR /myspeed
-COPY --from=build /myspeed/build /myspeed/build
-COPY --from=build /myspeed/server /myspeed/server
-COPY --from=build /myspeed/node_modules /myspeed/node_modules
-COPY --from=build /myspeed/package.json /myspeed/package.json
+COPY --from=client-build /client/build /myspeed/build
+COPY ./server /myspeed/server
+COPY ./deno.json /myspeed/deno.json
VOLUME ["/myspeed/data"]
EXPOSE 5216
-CMD ["node", "server"]
\ No newline at end of file
+CMD ["deno", "run", "--allow-all", "server/index.js"]
\ No newline at end of file
diff --git a/deno.json b/deno.json
new file mode 100644
index 00000000..1dd7280b
--- /dev/null
+++ b/deno.json
@@ -0,0 +1,44 @@
+{
+ "name": "myspeed",
+ "version": "1.0.9",
+ "exports": "./server/index.js",
+ "tasks": {
+ "client": "cd client && npm run dev",
+ "server": "deno run --allow-all --watch server/index.js",
+ "build": "cd client && npm run build",
+ "dev": "deno task server & deno task client",
+ "compile": "deno compile --allow-all --no-check --include build --output myspeed server/index.js",
+ "compile:linux": "deno compile --allow-all --no-check --include build --target x86_64-unknown-linux-gnu --output myspeed-linux server/index.js",
+ "compile:windows": "deno compile --allow-all --no-check --include build --target x86_64-pc-windows-msvc --output myspeed-windows.exe server/index.js",
+ "compile:macos": "deno compile --allow-all --no-check --include build --target x86_64-apple-darwin --output myspeed-macos server/index.js",
+ "compile:macos-arm": "deno compile --allow-all --no-check --include build --target aarch64-apple-darwin --output myspeed-macos-arm server/index.js"
+ },
+ "imports": {
+ "express": "npm:express@^5.2.1",
+ "sequelize": "npm:sequelize@^6.37.7",
+ "sqlite3": "npm:sqlite3@^5.1.7",
+ "mysql2": "npm:mysql2@^3.16.1",
+ "axios": "npm:axios@^1.13.2",
+ "bcrypt": "npm:bcrypt@^6.0.0",
+ "cron-validator": "npm:cron-validator@^1.4.0",
+ "node-schedule": "npm:node-schedule@^2.1.1",
+ "prom-client": "npm:prom-client@^15.1.3",
+ "satori": "npm:satori@^0.19.1",
+ "satori-html": "npm:satori-html@^0.3.2",
+ "@resvg/resvg-js": "npm:@resvg/resvg-js@^2.6.2",
+ "decompress": "npm:decompress@^4.2.1",
+ "decompress-targz": "npm:decompress-targz@^4.1.1",
+ "decompress-unzip": "npm:decompress-unzip@^4.0.1",
+ "tmp": "npm:tmp@^0.2.5",
+ "moment-timezone": "npm:moment-timezone@^0.5.45"
+ },
+ "nodeModulesDir": "auto",
+ "compilerOptions": {
+ "allowJs": true,
+ "checkJs": false
+ },
+ "allowScripts": [
+ "npm:bcrypt@6.0.0",
+ "npm:sqlite3@5.1.7"
+ ]
+}
diff --git a/package.json b/package.json
deleted file mode 100644
index 38bc7bc6..00000000
--- a/package.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "name": "myspeed",
- "version": "1.0.9",
- "scripts": {
- "client": "cd client && npm run dev",
- "server": "nodemon server",
- "build": "cd client && npm run build",
- "dev": "concurrently --kill-others-on-fail \"npm run server\" \"npm run client\""
- },
- "dependencies": {
- "@resvg/resvg-js": "^2.6.2",
- "axios": "^1.13.2",
- "bcrypt": "^6.0.0",
- "cron-validator": "^1.4.0",
- "decompress": "^4.2.1",
- "decompress-targz": "^4.1.1",
- "decompress-unzip": "^4.0.1",
- "express": "^5.2.1",
- "mysql2": "^3.16.1",
- "node-schedule": "^2.1.1",
- "prom-client": "^15.1.3",
- "satori": "^0.19.1",
- "satori-html": "^0.3.2",
- "sequelize": "^6.37.7",
- "sqlite3": "^5.1.7",
- "tmp": "^0.2.5"
- },
- "devDependencies": {
- "concurrently": "^9.2.1",
- "nodemon": "^3.1.11",
- "vitepress": "^1.6.4"
- }
-}
diff --git a/server/config/binaries.js b/server/config/binaries.js
index 3163976a..4989fe53 100644
--- a/server/config/binaries.js
+++ b/server/config/binaries.js
@@ -1,5 +1,5 @@
-module.exports.ooklaVersion = "1.2.0";
-module.exports.ooklaList = [
+export const ooklaVersion = "1.2.0";
+export const ooklaList = [
// MacOS
{os: 'darwin', arch: 'x64', suffix: 'macosx-x86_64.tgz'},
@@ -16,8 +16,8 @@ module.exports.ooklaList = [
{os: 'freebsd', arch: 'x64', suffix: 'freebsd12-x86_64.pkg'}
];
-module.exports.libreVersion = "1.0.10";
-module.exports.libreList = [
+export const libreVersion = "1.0.10";
+export const libreList = [
// MacOS
{os: 'darwin', arch: 'x64', suffix: 'darwin_amd64.tar.gz'},
{os: 'darwin', arch: 'arm64', suffix: 'darwin_arm64.tar.gz'},
@@ -40,8 +40,8 @@ module.exports.libreList = [
{os: 'freebsd', arch: 'arm64', suffix: 'freebsd_arm64.tar.gz'}
];
-module.exports.cloudflareVersion = "2.1.0";
-module.exports.cloudflareList = [
+export const cloudflareVersion = "2.1.0";
+export const cloudflareList = [
// MacOS
{os: 'darwin', arch: 'x64', suffix: 'cfspeedtest-x86_64-apple-darwin.tar.gz'},
{os: 'darwin', arch: 'arm64', suffix: 'cfspeedtest-aarch64-apple-darwin.tar.gz'},
@@ -53,4 +53,4 @@ module.exports.cloudflareList = [
// Linux
{os: 'linux', arch: 'x64', suffix: 'cfspeedtest-x86_64-unknown-linux-gnu.tar.gz'},
{os: 'linux', arch: 'arm64', suffix: 'cfspeedtest-aarch64-unknown-linux-gnu.tar.gz'}
-]
\ No newline at end of file
+];
\ No newline at end of file
diff --git a/server/config/database.js b/server/config/database.js
index 89d94e54..bc8e0b41 100644
--- a/server/config/database.js
+++ b/server/config/database.js
@@ -1,4 +1,4 @@
-const {Sequelize} = require('sequelize');
+import { Sequelize } from 'sequelize';
const STORAGE_PATH = `data/storage${process.env.PREVIEW_MODE === "true" ? "_preview" : ""}.db`;
@@ -6,19 +6,23 @@ Sequelize.DATE.prototype._stringify = () => {
return new Date().toISOString();
}
+let db;
+
if (process.env.DB_TYPE === "mysql") {
if (!process.env.DB_NAME || !process.env.DB_PASS || !process.env.DB_USER)
throw new Error("Missing database environment variables");
- module.exports = new Sequelize(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASS, {
+ db = new Sequelize(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASS, {
host: process.env.DB_HOST || "localhost",
dialect: 'mysql',
logging: false,
query: {raw: true}
});
} else if (!process.env.DB_TYPE || process.env.DB_TYPE === "sqlite") {
- module.exports = new Sequelize({dialect: 'sqlite', storage: STORAGE_PATH, logging: false, query: {raw: true}});
+ db = new Sequelize({dialect: 'sqlite', storage: STORAGE_PATH, logging: false, query: {raw: true}});
} else {
throw new Error("Invalid database type");
-}
\ No newline at end of file
+}
+
+export default db;
\ No newline at end of file
diff --git a/server/controller/config.js b/server/controller/config.js
index 8517278b..eca6f9f2 100644
--- a/server/controller/config.js
+++ b/server/controller/config.js
@@ -1,16 +1,20 @@
-const config = require("../models/Config");
-const node = require("../models/Node");
-const test = require("../models/Speedtests");
-const recommendations = require("../models/Recommendations");
-const integration = require("../models/IntegrationData");
-const {triggerEvent} = require("./integrations");
-const bcrypt = require('bcrypt');
-const timer = require('../tasks/timer');
-const cron = require('cron-validator');
-const db = require("../config/database");
-const fs = require('fs');
-const path = require('path');
-const interfaces = require('../util/loadInterfaces');
+import config from '../models/Config.js';
+import node from '../models/Node.js';
+import test from '../models/Speedtests.js';
+import recommendations from '../models/Recommendations.js';
+import integration from '../models/IntegrationData.js';
+import { triggerEvent } from './integrations.js';
+import bcrypt from 'bcrypt';
+import * as timer from '../tasks/timer.js';
+import cron from 'cron-validator';
+import db from '../config/database.js';
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import * as interfaces from '../util/loadInterfaces.js';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
const configDefaults = {
ping: "25",
@@ -26,7 +30,7 @@ const configDefaults = {
interface: "none"
}
-module.exports.insertDefaults = async () => {
+export const insertDefaults = async () => {
let insert = [];
for (let key in configDefaults) {
if (key !== "interface" && !(await config.findOne({where: {key: key}})))
@@ -44,16 +48,16 @@ module.exports.insertDefaults = async () => {
await config.bulkCreate(insert, {validate: true});
}
-module.exports.listAll = async () => {
+export const listAll = async () => {
return await config.findAll();
}
-module.exports.getValue = async (key) => {
+export const getValue = async (key) => {
return (await config.findByPk(key))?.value;
}
-module.exports.updateValue = async (key, newValue) => {
- if ((await this.getValue(key)) === undefined) return undefined;
+export const updateValue = async (key, newValue) => {
+ if ((await getValue(key)) === undefined) return undefined;
triggerEvent("configUpdated", {key: key, value: key === "password" ? "protected" : newValue})
.then(undefined);
@@ -61,7 +65,7 @@ module.exports.updateValue = async (key, newValue) => {
return await config.update({value: newValue}, {where: {key: key}});
}
-module.exports.getUsedStorage = async () => {
+export const getUsedStorage = async () => {
let size = 0;
if (process.env.DB_TYPE === "mysql") {
@@ -81,7 +85,7 @@ module.exports.getUsedStorage = async () => {
return {size, testCount: await test.count()};
}
-module.exports.validateInput = async (key, value) => {
+export const validateInput = async (key, value) => {
if (!value?.toString()) return "You need to provide the new value";
if ((key === "ping" || key === "download" || key === "upload") && /[^0-9.]/.test(value))
@@ -125,7 +129,7 @@ module.exports.validateInput = async (key, value) => {
return {value: value};
}
-module.exports.exportConfig = async () => {
+export const exportConfig = async () => {
let obj = {};
obj.config = {};
@@ -143,13 +147,13 @@ module.exports.exportConfig = async () => {
return obj;
}
-module.exports.importConfig = async (obj) => {
+export const importConfig = async (obj) => {
let configValues = obj.config;
for (let key in configValues) {
if (configDefaults[key] === undefined) continue
if (key === "password") continue;
- const validate = await this.validateInput(key, configValues[key]);
+ const validate = await validateInput(key, configValues[key]);
if (Object.keys(validate).length !== 1) return false;
if (key === "cron") {
@@ -183,7 +187,7 @@ module.exports.importConfig = async (obj) => {
return true;
}
-module.exports.factoryReset = async () => {
+export const factoryReset = async () => {
let configValues = await config.findAll();
for (let i = 0; i < configValues.length; i++) {
await config.update({value: configDefaults[configValues[i].key]}, {where: {key: configValues[i].key}});
diff --git a/server/controller/integrations.js b/server/controller/integrations.js
index 2aee7a5f..7ffcd5ac 100644
--- a/server/controller/integrations.js
+++ b/server/controller/integrations.js
@@ -1,6 +1,10 @@
-const path = require("path");
-const {readdirSync} = require("fs");
-const IntegrationData = require("../models/IntegrationData");
+import IntegrationData from '../models/IntegrationData.js';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
const integrations = {};
const events = {};
@@ -21,7 +25,7 @@ const triggerActivity = async (id, error = false) => {
await IntegrationData.update({lastActivity: new Date().toISOString(), activityFailed: error}, {where: {id: id}});
}
-module.exports.triggerEvent = async (name, data) => {
+export const triggerEvent = async (name, data) => {
if (!events[name]) return;
for (const module of events[name]) {
@@ -31,24 +35,31 @@ module.exports.triggerEvent = async (name, data) => {
}
}
-module.exports.initialize = () => readdirSync(path.join(__dirname, "../integrations")).forEach(async (file) => {
- if (file.endsWith(".js")) {
- const integrationName = file.replace(".js", "");
- integrations[integrationName] = require(path.join(__dirname, "../integrations", file))(registerEvent(integrationName));
+export const initialize = async () => {
+ const integrationsDir = path.join(__dirname, '..', 'integrations');
+
+ for await (const entry of Deno.readDir(integrationsDir)) {
+ if (!entry.isFile || !entry.name.endsWith('.js')) continue;
+
+ const integrationName = entry.name.replace('.js', '');
+ const filePath = path.join(integrationsDir, entry.name);
+
+ const module = await import(`file://${filePath}`);
+ integrations[integrationName] = module.default(registerEvent(integrationName));
console.log(`Integration "${integrationName}" loaded successfully`);
}
-});
+};
-module.exports.getActive = async () => {
+export const getActive = async () => {
const data = await IntegrationData.findAll();
if (!data) return null;
return data.map((item) => ({...item, data: JSON.parse(item.data)}));
}
-module.exports.getIntegrationById = (id) => IntegrationData.findOne({where: {id: id}});
+export const getIntegrationById = (id) => IntegrationData.findOne({where: {id: id}});
-module.exports.delete = async (id) => {
+export const deleteIntegration = async (id) => {
const data = await IntegrationData.findOne({where: {id}});
if (!data) return null;
@@ -56,7 +67,7 @@ module.exports.delete = async (id) => {
return true;
}
-module.exports.create = async (name, data) => {
+export const create = async (name, data) => {
const integration = integrations[name];
if (!integration) return null;
@@ -68,7 +79,7 @@ module.exports.create = async (name, data) => {
return created.id;
}
-module.exports.patch = async (id, data) => {
+export const patch = async (id, data) => {
const item = await IntegrationData.findOne({where: {id: id}});
if (!item) return null;
@@ -79,7 +90,7 @@ module.exports.patch = async (id, data) => {
return true;
}
-module.exports.getIntegrations = () => {
+export const getIntegrations = () => {
const result = {};
for (const [name, integration] of Object.entries(integrations)) {
@@ -95,9 +106,9 @@ module.exports.getIntegrations = () => {
return result;
};
-module.exports.getIntegration = (name) => integrations[name];
+export const getIntegration = (name) => integrations[name];
-module.exports.validateInput = (module, data) => {
+export const validateInput = (module, data) => {
const integration = integrations[module];
if (!integration) return false;
diff --git a/server/controller/node.js b/server/controller/node.js
index b7869fa7..bd5af85a 100644
--- a/server/controller/node.js
+++ b/server/controller/node.js
@@ -1,20 +1,20 @@
-const axios = require('axios');
-const nodes = require('../models/Node');
+import axios from 'axios';
+import nodes from '../models/Node.js';
-module.exports.listAll = async () => await nodes.findAll()
+export const listAll = async () => await nodes.findAll()
.then((result) => result.map((node) => ({...node, password: node.password !== null})));
-module.exports.create = async (name, url, password) => await nodes.create({name: name, url: url, password: password});
+export const create = async (name, url, password) => await nodes.create({name: name, url: url, password: password});
-module.exports.delete = async (nodeId) => await nodes.destroy({where: {id: nodeId}});
+export const deleteNode = async (nodeId) => await nodes.destroy({where: {id: nodeId}});
-module.exports.getOne = async (nodeId) => await nodes.findOne({where: {id: nodeId}});
+export const getOne = async (nodeId) => await nodes.findOne({where: {id: nodeId}});
-module.exports.updateName = async (nodeId, name) => await nodes.update({name: name}, {where: {id: nodeId}});
+export const updateName = async (nodeId, name) => await nodes.update({name: name}, {where: {id: nodeId}});
-module.exports.updatePassword = async (nodeId, password) => await nodes.update({password: password}, {where: {id: nodeId}});
+export const updatePassword = async (nodeId, password) => await nodes.update({password: password}, {where: {id: nodeId}});
-module.exports.checkStatus = async (url, password) => {
+export const checkStatus = async (url, password) => {
if (password === "none") password = undefined;
const api = await axios.get(url + "/api/config", {headers: {password: password}}).catch(() => {
return "INVALID_URL";
@@ -29,7 +29,7 @@ module.exports.checkStatus = async (url, password) => {
return "NODE_VALID";
}
-module.exports.proxyRequest = async (url, req, res) => {
+export const proxyRequest = async (url, req, res) => {
const response = await axios(url, {
method: req.method,
headers: req.headers,
diff --git a/server/controller/opengraph.js b/server/controller/opengraph.js
index 3451cadb..38902dab 100644
--- a/server/controller/opengraph.js
+++ b/server/controller/opengraph.js
@@ -1,8 +1,10 @@
-const fs = require("fs");
-const resvg = require("@resvg/resvg-js").Resvg;
-const moment = require("moment-timezone");
-const tests = require("../controller/speedtests");
-const axios = require("axios");
+import fs from 'node:fs';
+import { Resvg } from '@resvg/resvg-js';
+import moment from 'moment-timezone';
+import * as tests from './speedtests.js';
+import axios from 'axios';
+import { html } from 'satori-html';
+import satori from 'satori';
async function generateOpenGraphImage(req) {
const today = new Date();
@@ -17,14 +19,11 @@ async function generateOpenGraphImage(req) {
}
const fontPath = "/assets/fonts/inter-v12-latin-regular.ttf";
+ const localFontPath = `client/public${fontPath}`;
- const font =
- process.env.NODE_ENV === "production"
- ? (await axios.get(`${req.protocol}://${req.hostname}${fontPath}`)).data
- : await fs.promises.readFile(`client/public${fontPath}`);
-
- const html = (await import("satori-html")).html;
- const satori = (await import("satori")).default;
+ const font = fs.existsSync(localFontPath)
+ ? await fs.promises.readFile(localFontPath)
+ : (await axios.get(`${req.protocol}://${req.hostname}${fontPath}`)).data;
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const date = moment().tz(timeZone).format("MM/DD/YYYY");
@@ -170,9 +169,9 @@ async function generateOpenGraphImage(req) {
],
});
- const svg = new resvg(image);
+ const svg = new Resvg(image);
return svg.render().asPng();
}
-module.exports = generateOpenGraphImage;
+export default generateOpenGraphImage;
diff --git a/server/controller/pause.js b/server/controller/pause.js
index 8bc8d585..997e8fa5 100644
--- a/server/controller/pause.js
+++ b/server/controller/pause.js
@@ -1,21 +1,21 @@
-let currentState = false;
+let currentStateVar = false;
let updateTimer;
-module.exports.updateState = (newState) => {
- this.currentState = newState;
+export const updateState = (newState) => {
+ currentStateVar = newState;
}
-module.exports.resumeIn = (hours) => {
+export const resumeIn = (hours) => {
if (/[^0-9]/.test(hours)) return false;
if (updateTimer !== null)
clearTimeout(updateTimer);
- this.updateState(true);
- updateTimer = setTimeout(() => this.updateState(false), hours * 3600000); // time in hours
+ updateState(true);
+ updateTimer = setTimeout(() => updateState(false), hours * 3600000); // time in hours
return true;
}
-module.exports.currentState = currentState;
\ No newline at end of file
+export { currentStateVar as currentState };
\ No newline at end of file
diff --git a/server/controller/recommendations.js b/server/controller/recommendations.js
index d6ac436c..93f25f87 100644
--- a/server/controller/recommendations.js
+++ b/server/controller/recommendations.js
@@ -1,11 +1,11 @@
-const recommendations = require('../models/Recommendations');
-const {triggerEvent} = require("./integrations");
+import recommendations from '../models/Recommendations.js';
+import { triggerEvent } from './integrations.js';
-module.exports.getCurrent = async () => {
+export const getCurrent = async () => {
return await recommendations.findOne();
}
-module.exports.update = async (ping, download, upload) => {
+export const update = async (ping, download, upload) => {
const configuration = {ping: Math.round(ping), download: parseFloat(download.toFixed(2)),
upload: parseFloat(upload.toFixed(2))};
diff --git a/server/controller/servers.js b/server/controller/servers.js
index 27d9102e..68e6338d 100644
--- a/server/controller/servers.js
+++ b/server/controller/servers.js
@@ -1,8 +1,9 @@
-const fs = require("fs");
+import fs from 'node:fs';
+
let ooklaServers;
let libreServers;
-module.exports.getLibreServers = () => {
+export const getLibreServers = () => {
if (libreServers) return libreServers;
if (fs.existsSync("./data/servers/librespeed.json")) {
@@ -15,7 +16,7 @@ module.exports.getLibreServers = () => {
return [];
}
-module.exports.getOoklaServers = () => {
+export const getOoklaServers = () => {
if (ooklaServers) return ooklaServers;
if (fs.existsSync("./data/servers/ookla.json")) {
@@ -28,7 +29,7 @@ module.exports.getOoklaServers = () => {
return [];
}
-module.exports.getByMode = (mode) => {
- if (mode === "ookla") return this.getOoklaServers();
- if (mode === "libre") return this.getLibreServers();
+export const getByMode = (mode) => {
+ if (mode === "ookla") return getOoklaServers();
+ if (mode === "libre") return getLibreServers();
}
\ No newline at end of file
diff --git a/server/controller/speedtests.js b/server/controller/speedtests.js
index d41ae963..cf439477 100644
--- a/server/controller/speedtests.js
+++ b/server/controller/speedtests.js
@@ -1,19 +1,19 @@
-const tests = require('../models/Speedtests');
-const {Op, Sequelize} = require("sequelize");
-const {mapFixed, mapRounded} = require("../util/helpers");
+import tests from '../models/Speedtests.js';
+import { Op, Sequelize } from 'sequelize';
+import { mapFixed, mapRounded } from '../util/helpers.js';
-module.exports.create = async (ping, download, upload, time, serverId, type = "auto", resultId = null, error = null, jitter = null) => {
+export const create = async (ping, download, upload, time, serverId, type = "auto", resultId = null, error = null, jitter = null) => {
return (await tests.create({ping, jitter, download, upload, error, serverId, type, resultId, time, created: new Date().toISOString()})).id;
}
-module.exports.getOne = async (id) => {
+export const getOne = async (id) => {
let speedtest = await tests.findByPk(id);
if (speedtest === null) return null;
if (speedtest.error === null) delete speedtest.error;
return speedtest
}
-module.exports.listAll = async () => {
+export const listAll = async () => {
let dbEntries = await tests.findAll({order: [["created", "DESC"]]});
for (let dbEntry of dbEntries) {
if (dbEntry.error === null) delete dbEntry.error;
@@ -23,7 +23,7 @@ module.exports.listAll = async () => {
return dbEntries;
}
-module.exports.listTests = async (afterId, limit) => {
+export const listTests = async (afterId, limit) => {
limit = parseInt(limit) || 10;
let whereClause = {};
@@ -44,12 +44,12 @@ module.exports.listTests = async (afterId, limit) => {
return dbEntries;
}
-module.exports.deleteTests = async () => {
+export const deleteTests = async () => {
await tests.destroy({where: {}});
return true;
}
-module.exports.importTests = async (data) => {
+export const importTests = async (data) => {
if (!Array.isArray(data)) return false;
for (let entry of data) {
@@ -69,7 +69,7 @@ module.exports.importTests = async (data) => {
return true;
}
-module.exports.listStatistics = async (fromDate, toDate) => {
+export const listStatistics = async (fromDate, toDate) => {
const from = new Date(fromDate);
const to = new Date(toDate);
from.setHours(0, 0, 0, 0);
@@ -238,13 +238,13 @@ module.exports.listStatistics = async (fromDate, toDate) => {
};
}
-module.exports.deleteOne = async (id) => {
- if (await this.getOne(id) === null) return false;
+export const deleteOne = async (id) => {
+ if (await getOne(id) === null) return false;
await tests.destroy({where: {id: id}});
return true;
}
-module.exports.removeOld = async () => {
+export const removeOld = async () => {
await tests.destroy({
where: {
created: process.env.DB_TYPE === "mysql"
@@ -255,7 +255,7 @@ module.exports.removeOld = async () => {
return true;
}
-module.exports.getLatest = async () => {
+export const getLatest = async () => {
let latest = await tests.findOne({order: [["created", "DESC"]]});
if (latest === null) return undefined;
if (latest.error === null) delete latest.error;
diff --git a/server/index.js b/server/index.js
index b6cb1037..e564d277 100755
--- a/server/index.js
+++ b/server/index.js
@@ -1,7 +1,57 @@
-const express = require('express');
-const path = require('path');
-const timerTask = require('./tasks/timer');
-const integrationTask = require('./tasks/integrations');
+import express from 'express';
+import path from 'node:path';
+import fs from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import * as timerTask from './tasks/timer.js';
+import * as integrationTask from './tasks/integrations.js';
+import './util/createFolders.js';
+import './util/loadServers.js';
+import errorHandler from './util/errorHandler.js';
+import errorMiddleware from './middlewares/error.js';
+import configRoutes from './routes/config.js';
+import speedtestsRoutes from './routes/speedtests.js';
+import systemRoutes from './routes/system.js';
+import storageRoutes from './routes/storage.js';
+import recommendationsRoutes from './routes/recommendations.js';
+import nodesRoutes from './routes/nodes.js';
+import integrationsRoutes from './routes/integrations.js';
+import prometheusRoutes from './routes/prometheus.js';
+import opengraphRoutes from './routes/opengraph.js';
+import db from './config/database.js';
+import * as config from './controller/config.js';
+import { initialize as initializeIntegrations } from './controller/integrations.js';
+import { requestInterfaces } from './util/loadInterfaces.js';
+import { load as loadCli } from './util/loadCli.js';
+import { removeOld } from './tasks/speedtest.js';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+const devModeHtml = `
+
+
+
+
+
+ MySpeed [Dev Mode]
+
+
+
+ 
MySpeed Dev Mode
+ Your MySpeed instance is currently in development mode.
Please build the client or use a production binary.
+
+
+
+`;
const app = express();
@@ -9,51 +59,51 @@ app.disable('x-powered-by');
const port = process.env.SERVER_PORT || 5216;
-require('./util/createFolders');
-require('./util/loadServers');
-
-process.on('uncaughtException', err => require('./util/errorHandler')(err));
+process.on('uncaughtException', err => errorHandler(err));
app.use(express.json());
-app.use(require('./middlewares/error'));
+app.use(errorMiddleware);
-app.use("/api/config", require('./routes/config'));
-app.use("/api/speedtests", require('./routes/speedtests'));
-app.use("/api/info", require('./routes/system'));
-app.use("/api/storage", require('./routes/storage'));
-app.use("/api/recommendations", require('./routes/recommendations'));
-app.use("/api/nodes", require('./routes/nodes'));
-app.use("/api/integrations", require('./routes/integrations'));
-app.use("/api/prometheus", require('./routes/prometheus'));
-app.use('/api/opengraph', require('./routes/opengraph'));
+app.use("/api/config", configRoutes);
+app.use("/api/speedtests", speedtestsRoutes);
+app.use("/api/info", systemRoutes);
+app.use("/api/storage", storageRoutes);
+app.use("/api/recommendations", recommendationsRoutes);
+app.use("/api/nodes", nodesRoutes);
+app.use("/api/integrations", integrationsRoutes);
+app.use("/api/prometheus", prometheusRoutes);
+app.use('/api/opengraph', opengraphRoutes);
app.use("/api*all", (req, res) => res.status(404).json({message: "Route not found"}));
-if (process.env.NODE_ENV === 'production') {
- app.use(express.static(path.join(__dirname, '../build')));
+let buildPath = path.join(__dirname, '..', 'build');
+let buildExists = fs.existsSync(buildPath);
- app.get('*all', (req, res) => res.sendFile(path.join(__dirname, '../build', 'index.html')));
-} else {
- app.get("*all", (req, res) => res.status(500).sendFile(path.join(__dirname, 'templates', 'env.html')));
+if (!buildExists) {
+ buildPath = path.join(process.cwd(), 'build');
+ buildExists = fs.existsSync(buildPath);
}
-let db = require("./config/database");
+if (buildExists) {
+ app.use(express.static(buildPath));
+ app.get('*all', (req, res) => res.sendFile(path.join(buildPath, 'index.html')));
+} else {
+ app.get("*all", (req, res) => res.status(500).type('html').send(devModeHtml));
+}
const run = async () => {
- const config = require('./controller/config');
-
await db.sync({alter: true, force: false});
- await require('./controller/integrations').initialize();
+ await initializeIntegrations();
- await require('./util/loadInterfaces').requestInterfaces();
- setInterval(() => require('./util/loadInterfaces').requestInterfaces(), 3600000);
+ await requestInterfaces();
+ setInterval(() => requestInterfaces(), 3600000);
- if (process.env.PREVIEW_MODE !== "true") await require('./util/loadCli').load();
+ if (process.env.PREVIEW_MODE !== "true") await loadCli();
await config.insertDefaults();
timerTask.startTimer(await config.getValue("cron"));
- setInterval(async () => require('./tasks/speedtest').removeOld(), 60000);
+ setInterval(async () => removeOld(), 60000);
integrationTask.startTimer();
if (process.env.RUN_TEST_ON_STARTUP === "true") {
diff --git a/server/integrations/discord.js b/server/integrations/discord.js
index 7ca5a7b4..69a87ea3 100644
--- a/server/integrations/discord.js
+++ b/server/integrations/discord.js
@@ -1,10 +1,10 @@
-const axios = require("axios");
-const {replaceVariables} = require("../util/helpers");
+import axios from "axios";
+import { replaceVariables } from "../util/helpers.js";
const defaults = {
finished: ":sparkles: **A speedtest is finished**\n > :ping_pong: `Ping`: %ping% ms (±%jitter% ms)\n > :arrow_up: `Upload`: %upload% Mbps\n > :arrow_down: `Download`: %download% Mbps",
failed: ":x: **A speedtest has failed**\n > `Reason`: %error%"
-}
+};
const postWebhook = async (url, username, color, message, triggerActivity) => {
axios.post(url, {
@@ -13,9 +13,9 @@ const postWebhook = async (url, username, color, message, triggerActivity) => {
})
.then(() => triggerActivity())
.catch(() => triggerActivity(true));
-}
+};
-module.exports = (registerEvent) => {
+export default (registerEvent) => {
registerEvent('testFinished', async (integration, data, activity) => {
if (integration.data.send_finished)
await postWebhook(integration.data.url, integration.data.display_name || "MySpeed", 4572762,
@@ -39,4 +39,4 @@ module.exports = (registerEvent) => {
{name: "error_message", type: "textarea", required: false}
]
};
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/server/integrations/gotify.js b/server/integrations/gotify.js
index 761f27eb..215607f7 100644
--- a/server/integrations/gotify.js
+++ b/server/integrations/gotify.js
@@ -1,19 +1,19 @@
-const axios = require("axios");
-const {replaceVariables} = require("../util/helpers");
+import axios from "axios";
+import { replaceVariables } from "../util/helpers.js";
const defaults = {
finished: "A speedtest is finished:\nPing: %ping% ms (±%jitter% ms)\nUpload: %upload% Mbps\nDownload: %download% Mbps",
failed: "A speedtest has failed. Reason: %error%"
-}
+};
const postWebhook = async (url, key, triggerActivity, message, priority) => {
axios.post(`${url}/message`, {message, priority: parseInt(priority)},
{headers: {"Authorization": "Bearer " + key}})
.then(() => triggerActivity())
.catch(() => triggerActivity(true));
-}
+};
-module.exports = (registerEvent) => {
+export default (registerEvent) => {
registerEvent('testFinished', async (integration, data, activity) => {
if (integration.data.send_finished)
await postWebhook(integration.data.url, integration.data.key, activity,
@@ -39,4 +39,4 @@ module.exports = (registerEvent) => {
{name: "error_message", type: "textarea", required: false}
]
};
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/server/integrations/healthChecks.js b/server/integrations/healthChecks.js
index fb5168b6..5bff7ee7 100644
--- a/server/integrations/healthChecks.js
+++ b/server/integrations/healthChecks.js
@@ -1,4 +1,4 @@
-const axios = require("axios");
+import axios from "axios";
const sendPing = async (url, path, error, triggerActivity) => {
if (url == null) return;
@@ -7,9 +7,9 @@ const sendPing = async (url, path, error, triggerActivity) => {
await axios.post(url, error, {headers: {"user-agent": "MySpeed/HealthAgent"}})
.then(() => triggerActivity())
.catch(() => triggerActivity(true));
-}
+};
-module.exports = (registerEvent) => {
+export default (registerEvent) => {
registerEvent('minutePassed', async (integration, data, triggerActivity) => {
if (integration.data.url) await sendPing(integration.data.url, undefined, undefined, triggerActivity);
});
@@ -32,4 +32,4 @@ module.exports = (registerEvent) => {
{name: "url", type: "text", required: true, regex: /https?:\/\/.+/},
]
};
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/server/integrations/pushover.js b/server/integrations/pushover.js
index 98ffe94b..220e6788 100644
--- a/server/integrations/pushover.js
+++ b/server/integrations/pushover.js
@@ -1,14 +1,14 @@
-const axios = require("axios");
-const {replaceVariables} = require("../util/helpers");
+import axios from "axios";
+import { replaceVariables } from "../util/helpers.js";
const BASE_URL = "https://api.pushover.net/1";
const defaults = {
finished: "A speedtest is finished:\nPing: %ping% ms (±%jitter% ms)\nUpload: %upload% Mbps\nDownload: %download% Mbps",
failed: "A speedtest has failed. Reason: %error%"
-}
+};
-module.exports = (registerEvent) => {
+export default (registerEvent) => {
registerEvent('testFinished', async (integration, data, triggerActivity) => {
if (!integration.data.send_finished) return;
@@ -44,4 +44,4 @@ module.exports = (registerEvent) => {
{name: "error_message", type: "textarea", required: false}
]
};
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/server/integrations/telegram.js b/server/integrations/telegram.js
index 88a6f288..f7a31034 100644
--- a/server/integrations/telegram.js
+++ b/server/integrations/telegram.js
@@ -1,10 +1,10 @@
-const axios = require("axios");
-const {replaceVariables} = require("../util/helpers");
+import axios from "axios";
+import { replaceVariables } from "../util/helpers.js";
const defaults = {
finished: "✨ *A speedtest is finished*\n🏓 `Ping`: %ping% ms (±%jitter% ms)\n🔼 `Upload`: %upload% Mbps\n🔽 `Download`: %download% Mbps",
failed: "❌ *A speedtest has failed*\n`Reason`: %error%"
-}
+};
const postWebhook = async (token, chatId, message, triggerActivity) => {
axios.post(`https://api.telegram.org/bot${token}/sendMessage`, {
@@ -12,9 +12,9 @@ const postWebhook = async (token, chatId, message, triggerActivity) => {
})
.then(() => triggerActivity())
.catch(() => triggerActivity(true));
-}
+};
-module.exports = (registerEvent) => {
+export default (registerEvent) => {
registerEvent('testFinished', async (integration, data, activity) => {
if (integration.data.send_finished)
await postWebhook(integration.data.token, integration.data.chat_id,
@@ -38,4 +38,4 @@ module.exports = (registerEvent) => {
{name: "error_message", type: "textarea", required: false}
]
};
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/server/integrations/webhook.js b/server/integrations/webhook.js
index c81f449c..45221a52 100644
--- a/server/integrations/webhook.js
+++ b/server/integrations/webhook.js
@@ -1,12 +1,12 @@
-const axios = require("axios");
+import axios from "axios";
const postWebhook = async (url, event, data, triggerActivity) => {
axios.post(url, {event, data}, {headers: {"user-agent": "MySpeed/WebhookAgent"}})
.then(() => triggerActivity())
.catch(() => triggerActivity(true));
-}
+};
-module.exports = (registerEvent) => {
+export default (registerEvent) => {
registerEvent('testStarted', async (integration, data, activity) => {
if (integration.data.send_started) await postWebhook(integration.data.url, "TEST_STARTED", undefined, activity);
});
@@ -43,4 +43,4 @@ module.exports = (registerEvent) => {
{name: "send_config_updates", type: "boolean", required: false}
]
};
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/server/middlewares/error.js b/server/middlewares/error.js
index 4e6ad600..53e72961 100644
--- a/server/middlewares/error.js
+++ b/server/middlewares/error.js
@@ -1,5 +1,5 @@
-module.exports = (err, req, res, next) => {
+export default (err, req, res, next) => {
if (!(err instanceof SyntaxError)) return next();
res.status(400).json({message: "You need to provide a valid JSON body"});
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/server/middlewares/password.js b/server/middlewares/password.js
index 3896986e..2976827d 100644
--- a/server/middlewares/password.js
+++ b/server/middlewares/password.js
@@ -1,7 +1,7 @@
-const config = require('../controller/config');
-const bcrypt = require('bcrypt');
+import * as config from '../controller/config.js';
+import bcrypt from 'bcrypt';
-module.exports = (allowViewAccess) => async (req, res, next) => {
+export default (allowViewAccess) => async (req, res, next) => {
if (process.env.PREVIEW_MODE === "true") return next();
let passwordHash = await config.getValue("password");
@@ -23,4 +23,4 @@ module.exports = (allowViewAccess) => async (req, res, next) => {
}
return res.status(401).json({message: "Please provide the correct password in the header"});
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/server/middlewares/passwordWrapper.js b/server/middlewares/passwordWrapper.js
index 3493e11c..5016cbb8 100644
--- a/server/middlewares/passwordWrapper.js
+++ b/server/middlewares/passwordWrapper.js
@@ -1,4 +1,4 @@
-const passwordMiddleware = require('./password');
+import passwordMiddleware from './password.js';
const passwordWrapper = (allowViewAccess, customResponseHandler) => async (req, res, next) => {
// Intercept the response send method
@@ -22,4 +22,4 @@ const passwordWrapper = (allowViewAccess, customResponseHandler) => async (req,
}
};
-module.exports = passwordWrapper;
+export default passwordWrapper;
diff --git a/server/models/Config.js b/server/models/Config.js
index 1c03504f..76de01d1 100644
--- a/server/models/Config.js
+++ b/server/models/Config.js
@@ -1,7 +1,7 @@
-const Sequelize = require('sequelize');
-const db = require("../config/database");
+import Sequelize from 'sequelize';
+import db from '../config/database.js';
-module.exports = db.define("config", {
+export default db.define("config", {
key: {
type: Sequelize.STRING,
primaryKey: true,
diff --git a/server/models/IntegrationData.js b/server/models/IntegrationData.js
index 5946f057..e089e570 100644
--- a/server/models/IntegrationData.js
+++ b/server/models/IntegrationData.js
@@ -1,7 +1,7 @@
-const Sequelize = require('sequelize');
-const db = require("../config/database");
+import Sequelize from 'sequelize';
+import db from '../config/database.js';
-module.exports = db.define("integration_data", {
+export default db.define("integration_data", {
id: {
type: Sequelize.STRING,
required: true,
diff --git a/server/models/Node.js b/server/models/Node.js
index fb543b11..98a98126 100644
--- a/server/models/Node.js
+++ b/server/models/Node.js
@@ -1,7 +1,7 @@
-const Sequelize = require('sequelize');
-const db = require("../config/database");
+import Sequelize from 'sequelize';
+import db from '../config/database.js';
-module.exports = db.define("nodes", {
+export default db.define("nodes", {
name: {
type: Sequelize.STRING,
defaultValue: "MySpeed Server"
diff --git a/server/models/Recommendations.js b/server/models/Recommendations.js
index 7d23775c..5f588ec9 100644
--- a/server/models/Recommendations.js
+++ b/server/models/Recommendations.js
@@ -1,7 +1,7 @@
-const Sequelize = require('sequelize');
-const db = require("../config/database");
+import Sequelize from 'sequelize';
+import db from '../config/database.js';
-module.exports = db.define("recommendations", {
+export default db.define("recommendations", {
ping: {
type: Sequelize.INTEGER,
allowNull: false
diff --git a/server/models/Speedtests.js b/server/models/Speedtests.js
index 0f81b2ca..774cc85f 100644
--- a/server/models/Speedtests.js
+++ b/server/models/Speedtests.js
@@ -1,7 +1,7 @@
-const Sequelize = require('sequelize');
-const db = require("../config/database");
+import Sequelize from 'sequelize';
+import db from '../config/database.js';
-module.exports = db.define("speedtests", {
+export default db.define("speedtests", {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
diff --git a/server/routes/config.js b/server/routes/config.js
index 4ebeaaf0..3eaae85a 100644
--- a/server/routes/config.js
+++ b/server/routes/config.js
@@ -1,7 +1,9 @@
-const app = require('express').Router();
-const config = require('../controller/config');
-const timer = require('../tasks/timer');
-const password = require('../middlewares/password');
+import express from 'express';
+import * as config from '../controller/config.js';
+import * as timer from '../tasks/timer.js';
+import password from '../middlewares/password.js';
+
+const app = express.Router();
app.get("/", password(true), async (req, res) => {
let configValues = {};
@@ -34,4 +36,4 @@ app.patch("/:key", password(false), async (req, res) => {
res.json({message: `The key '${req.params.key}' has been successfully updated`});
});
-module.exports = app;
\ No newline at end of file
+export default app;
\ No newline at end of file
diff --git a/server/routes/integrations.js b/server/routes/integrations.js
index 0a47d773..dea71a61 100644
--- a/server/routes/integrations.js
+++ b/server/routes/integrations.js
@@ -1,7 +1,9 @@
-const app = require('express').Router();
-const integrations = require('../controller/integrations');
-const password = require('../middlewares/password');
-const {validateInput} = require("../controller/integrations");
+import express from 'express';
+import * as integrations from '../controller/integrations.js';
+import password from '../middlewares/password.js';
+import { validateInput } from '../controller/integrations.js';
+
+const app = express.Router();
app.get("/", password(false), (req, res) => res.json(integrations.getIntegrations()));
@@ -43,10 +45,10 @@ app.delete("/:id", password(false), async (req, res) => {
if (process.env.PREVIEW_MODE === "true")
return res.status(403).json({message: "For security reasons, you can't delete integrations in preview mode"});
- const result = await integrations.delete(req.params.id);
+ const result = await integrations.deleteIntegration(req.params.id);
if (result === null) return res.status(404).json({message: "Integration not found"});
return res.json({message: "Integration deleted"});
});
-module.exports = app;
\ No newline at end of file
+export default app;
\ No newline at end of file
diff --git a/server/routes/nodes.js b/server/routes/nodes.js
index 7d71ce69..b9aa65c6 100644
--- a/server/routes/nodes.js
+++ b/server/routes/nodes.js
@@ -1,6 +1,8 @@
-const app = require('express').Router();
-const nodes = require('../controller/node');
-const password = require("../middlewares/password");
+import express from 'express';
+import * as nodes from '../controller/node.js';
+import password from '../middlewares/password.js';
+
+const app = express.Router();
app.get("/", password(false), async (req, res) => {
return res.json(await nodes.listAll());
@@ -32,7 +34,7 @@ app.delete("/:nodeId", password(false), async (req, res) => {
const node = await nodes.getOne(req.params.nodeId);
if (node === null) return res.status(404).json({message: "Node not found"});
- await nodes.delete(req.params.nodeId);
+ await nodes.deleteNode(req.params.nodeId);
res.json({message: "Node successfully deleted"});
});
@@ -82,4 +84,4 @@ app.all("/:nodeId/*route", password(false), async (req, res) => {
await nodes.proxyRequest(url, req, res);
});
-module.exports = app;
\ No newline at end of file
+export default app;
\ No newline at end of file
diff --git a/server/routes/opengraph.js b/server/routes/opengraph.js
index f0200370..1be02741 100644
--- a/server/routes/opengraph.js
+++ b/server/routes/opengraph.js
@@ -1,7 +1,8 @@
-const express = require("express");
+import express from 'express';
+import passwordWrapper from '../middlewares/passwordWrapper.js';
+import generateOpenGraphImage from '../controller/opengraph.js';
+
const app = express.Router();
-const passwordWrapper = require('../middlewares/passwordWrapper');
-const generateOpenGraphImage = require("../controller/opengraph");
app.get("/image", passwordWrapper(true, (req, res) => {
// If there is a password set and the user does not want others to view their test data, return the project banner
@@ -21,4 +22,4 @@ app.get("/image", passwordWrapper(true, (req, res) => {
}
});
-module.exports = app;
+export default app;
diff --git a/server/routes/prometheus.js b/server/routes/prometheus.js
index fdc66da9..5556cce0 100644
--- a/server/routes/prometheus.js
+++ b/server/routes/prometheus.js
@@ -1,9 +1,10 @@
-const express = require('express');
+import express from 'express';
+import * as testController from '../controller/speedtests.js';
+import promClient from 'prom-client';
+import * as config from '../controller/config.js';
+import bcrypt from 'bcrypt';
+
const app = express.Router();
-const testController = require('../controller/speedtests');
-const promClient = require('prom-client');
-const config = require('../controller/config');
-const bcrypt = require('bcrypt');
const pingGauge = new promClient.Gauge({name: 'myspeed_ping', help: 'Current ping in ms'});
const jitterGauge = new promClient.Gauge({name: 'myspeed_jitter', help: 'Current jitter in ms'});
@@ -51,4 +52,4 @@ app.get('/metrics', async (req, res) => {
res.end(await promClient.register.metrics());
});
-module.exports = app;
\ No newline at end of file
+export default app;
\ No newline at end of file
diff --git a/server/routes/recommendations.js b/server/routes/recommendations.js
index 8865efcf..21752a0a 100644
--- a/server/routes/recommendations.js
+++ b/server/routes/recommendations.js
@@ -1,6 +1,8 @@
-const app = require('express').Router();
-const recommendations = require('../controller/recommendations');
-const password = require('../middlewares/password');
+import express from 'express';
+import * as recommendations from '../controller/recommendations.js';
+import password from '../middlewares/password.js';
+
+const app = express.Router();
app.get("/", password(false), async (req, res) => {
let currentRecommendations = await recommendations.getCurrent();
@@ -9,4 +11,4 @@ app.get("/", password(false), async (req, res) => {
return res.json(currentRecommendations);
});
-module.exports = app;
\ No newline at end of file
+export default app;
\ No newline at end of file
diff --git a/server/routes/speedtests.js b/server/routes/speedtests.js
index 75ed22fa..251fb689 100644
--- a/server/routes/speedtests.js
+++ b/server/routes/speedtests.js
@@ -1,9 +1,11 @@
-const app = require('express').Router();
-const tests = require('../controller/speedtests');
-const pauseController = require('../controller/pause');
-const config = require('../controller/config');
-const testTask = require("../tasks/speedtest");
-const password = require('../middlewares/password');
+import express from 'express';
+import * as tests from '../controller/speedtests.js';
+import * as pauseController from '../controller/pause.js';
+import * as config from '../controller/config.js';
+import * as testTask from '../tasks/speedtest.js';
+import password from '../middlewares/password.js';
+
+const app = express.Router();
app.get("/", password(true), async (req, res) => {
if (req.query.limit && /[^0-9]/.test(req.query.limit))
@@ -72,4 +74,4 @@ app.delete("/:id", password(false), async (req, res) => {
res.json({message: "Successfully deleted the provided speedtest"});
});
-module.exports = app;
\ No newline at end of file
+export default app;
\ No newline at end of file
diff --git a/server/routes/storage.js b/server/routes/storage.js
index bb46f8cc..15803ee4 100644
--- a/server/routes/storage.js
+++ b/server/routes/storage.js
@@ -1,7 +1,9 @@
-const app = require('express').Router();
-const tests = require('../controller/speedtests');
-const config = require('../controller/config');
-const password = require('../middlewares/password');
+import express from 'express';
+import * as tests from '../controller/speedtests.js';
+import * as config from '../controller/config.js';
+import password from '../middlewares/password.js';
+
+const app = express.Router();
app.get("/", password(false), async (req, res) => {
res.json(await config.getUsedStorage());
@@ -58,4 +60,4 @@ app.delete("/config", password(false), async (req, res) => {
res.status(result ? 200 : 500).json({message: result ? "Config reset" : "Error resetting config"});
});
-module.exports = app;
\ No newline at end of file
+export default app;
\ No newline at end of file
diff --git a/server/routes/system.js b/server/routes/system.js
index 08378fcf..702dd669 100644
--- a/server/routes/system.js
+++ b/server/routes/system.js
@@ -1,10 +1,13 @@
-const app = require('express').Router();
-const version = require('../../package.json').version;
+import express from 'express';
+import denoConfig from '../../deno.json' with { type: 'json' };
+import axios from 'axios';
+import password from '../middlewares/password.js';
+import * as serverController from '../controller/servers.js';
+import * as interfaces from '../util/loadInterfaces.js';
+
+const version = denoConfig.version;
const remote_url = "https://api.github.com/repos/gnmyt/myspeed/releases/latest";
-const axios = require('axios');
-const password = require('../middlewares/password');
-const serverController = require('../controller/servers');
-const interfaces = require('../util/loadInterfaces');
+const app = express.Router();
app.get("/version", password(false), async (req, res) => {
if (process.env.PREVIEW_MODE === "true") return res.json({local: version, remote: "0"});
@@ -27,4 +30,4 @@ app.get("/interfaces", password(false), async (req, res) => {
res.json(interfaces.interfaces);
});
-module.exports = app;
\ No newline at end of file
+export default app;
\ No newline at end of file
diff --git a/server/tasks/integrations.js b/server/tasks/integrations.js
index dd449f8e..100d3b9f 100644
--- a/server/tasks/integrations.js
+++ b/server/tasks/integrations.js
@@ -1,40 +1,40 @@
-const schedule = require('node-schedule');
-const {triggerEvent} = require("../controller/integrations");
+import schedule from 'node-schedule';
+import { triggerEvent } from "../controller/integrations.js";
let currentState = "ping";
let job;
-module.exports.setState = (state = "ping") => {
+export const setState = (state = "ping") => {
currentState = state;
-}
+};
-module.exports.sendPing = async (type, message) => {
+export const sendPing = async (type, message) => {
await triggerEvent("minutePassed", {type, message});
-}
+};
-module.exports.sendCurrent = async () => {
- if (currentState === "ping") await this.sendPing();
-}
+export const sendCurrent = async () => {
+ if (currentState === "ping") await sendPing();
+};
-module.exports.sendError = async (error = "Unknown error") => {
+export const sendError = async (error = "Unknown error") => {
await triggerEvent("testFailed", error);
-}
+};
-module.exports.sendRunning = async () => {
+export const sendRunning = async () => {
await triggerEvent("testStarted");
-}
+};
-module.exports.sendFinished = async (data) => {
+export const sendFinished = async (data) => {
await triggerEvent("testFinished", data);
-}
+};
-module.exports.startTimer = () => {
- job = schedule.scheduleJob('* * * * *', () => this.sendCurrent());
-}
+export const startTimer = () => {
+ job = schedule.scheduleJob('* * * * *', () => sendCurrent());
+};
-module.exports.stopTimer = () => {
+export const stopTimer = () => {
if (job !== undefined) {
job.cancel();
job = undefined;
}
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/server/tasks/speedtest.js b/server/tasks/speedtest.js
index 412edae1..2fdc88d8 100644
--- a/server/tasks/speedtest.js
+++ b/server/tasks/speedtest.js
@@ -1,15 +1,15 @@
-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");
+import speedTest from '../util/speedtest.js';
+import * as tests from '../controller/speedtests.js';
+import * as config from '../controller/config.js';
+import * as controller from "../controller/recommendations.js";
+import * as parseData from '../util/providers/parseData.js';
+import { setState, sendRunning, sendError, sendFinished } from "./integrations.js";
+import * as serverController from "../controller/servers.js";
-let isRunning = false;
+let _isRunning = false;
const setRunning = (running, sendRequest = true) => {
- isRunning = running;
+ _isRunning = running;
if (running) {
setState("running");
@@ -33,7 +33,7 @@ const createRecommendations = async () => {
}
}
-module.exports.run = async (retryAuto = false) => {
+export const run = async (retryAuto = false) => {
setRunning(true);
let mode = await config.getValue("provider");
@@ -76,10 +76,10 @@ module.exports.run = async (retryAuto = false) => {
return {...speedtest, serverId}
}
-module.exports.create = async (type = "auto", retried = false) => {
+export const create = async (type = "auto", retried = false) => {
const mode = await config.getValue("provider");
if (mode === "none") return 400;
- if (isRunning && !retried) return 500;
+ if (_isRunning && !retried) return 500;
try {
let test;
@@ -91,7 +91,7 @@ module.exports.create = async (type = "auto", retried = false) => {
upload: {bandwidth: 125 * 100000 * (Math.random() + 0.5), elapsed: 10000},
}
} else {
- test = await this.run(retried);
+ test = await run(retried);
}
let {ping, jitter, download, upload, time, resultId} = await parseData.parseData(process.env.PREVIEW_MODE === "true" ?
@@ -104,7 +104,7 @@ module.exports.create = async (type = "auto", retried = false) => {
sendFinished({ping, jitter, download, upload, time}).then(() => "");
} catch (e) {
console.log(e)
- if (!retried) return this.create(type, true);
+ if (!retried) return create(type, true);
let testResult = await tests.create(-1, -1, -1, null, 0, type, null, e.message);
await sendError(e.message);
setRunning(false, false);
@@ -112,8 +112,8 @@ module.exports.create = async (type = "auto", retried = false) => {
}
}
-module.exports.isRunning = () => isRunning;
+export const isRunning = () => _isRunning;
-module.exports.removeOld = async () => {
+export const removeOld = async () => {
await tests.removeOld();
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/server/tasks/timer.js b/server/tasks/timer.js
index d362c282..981c8ee5 100644
--- a/server/tasks/timer.js
+++ b/server/tasks/timer.js
@@ -1,28 +1,29 @@
-const pauseController = require('../controller/pause');
-const schedule = require('node-schedule');
-const {isValidCron} = require("cron-validator");
+import * as pauseController from '../controller/pause.js';
+import schedule from 'node-schedule';
+import { isValidCron } from "cron-validator";
+import { create as createSpeedtest } from './speedtest.js';
let job;
-module.exports.startTimer = (cron) => {
+export const startTimer = (cron) => {
if (!isValidCron(cron)) return;
- job = schedule.scheduleJob(cron, () => this.runTask());
-}
+ job = schedule.scheduleJob(cron, () => runTask());
+};
-module.exports.runTask = async () => {
+export const runTask = async () => {
if (pauseController.currentState) {
console.warn("Speedtests currently paused. Trying again later...");
return;
}
- await require('./speedtest').create("auto");
-}
+ await createSpeedtest("auto");
+};
-module.exports.stopTimer = () => {
+export const stopTimer = () => {
if (job !== undefined) {
job.cancel();
job = undefined;
}
-}
+};
-module.exports.job = job;
\ No newline at end of file
+export { job };
\ No newline at end of file
diff --git a/server/util/createFolders.js b/server/util/createFolders.js
index 7d26cff7..5e7400f2 100644
--- a/server/util/createFolders.js
+++ b/server/util/createFolders.js
@@ -1,13 +1,17 @@
-const fs = require('fs');
+import fs from 'node:fs';
+import path from 'node:path';
+
+const baseDir = process.cwd();
const neededFolder = ["data", "bin", "data/logs", "data/servers"];
neededFolder.forEach(folder => {
- if (!fs.existsSync(folder)) {
+ const fullPath = path.join(baseDir, folder);
+ if (!fs.existsSync(fullPath)) {
try {
- fs.mkdirSync(folder, {recursive: true});
+ fs.mkdirSync(fullPath, {recursive: true});
} catch (e) {
- console.error("Could not create the data folder. Please check the permission");
+ console.error(`Could not create the ${folder} folder. Please check the permission`);
process.exit(0);
}
}
diff --git a/server/util/errorHandler.js b/server/util/errorHandler.js
index b37cbe18..88b5ac10 100644
--- a/server/util/errorHandler.js
+++ b/server/util/errorHandler.js
@@ -1,7 +1,7 @@
-const fs = require("fs");
+import fs from "node:fs";
const filePath = process.cwd() + "/data/logs/error.log";
-module.exports = (error) => {
+export default (error) => {
const date = new Date().toLocaleString();
const lineStarter = fs.existsSync(filePath) ? "\n\n" : "# Found a bug? Report it here: https://github.com/gnmyt/myspeed/issues\n\n";
@@ -12,4 +12,4 @@ module.exports = (error) => {
process.exit(1);
});
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/server/util/helpers.js b/server/util/helpers.js
index 077ab67d..3a4551dd 100644
--- a/server/util/helpers.js
+++ b/server/util/helpers.js
@@ -1,16 +1,16 @@
-module.exports.replaceVariables = (message, variables) => {
+export const replaceVariables = (message, variables) => {
for (const variable in variables)
message = message.replace(`%${variable}%`, variables[variable]);
return message;
-}
+};
-module.exports.mapFixed = (entries, type) => ({
+export const mapFixed = (entries, type) => ({
min: Math.min(...entries.map((entry) => entry[type])),
max: Math.max(...entries.map((entry) => entry[type])),
avg: parseFloat((entries.reduce((a, b) => a + b[type], 0) / entries.length).toFixed(2))
});
-module.exports.mapRounded = (entries, type) => ({
+export const mapRounded = (entries, type) => ({
min: Math.min(...entries.map((entry) => entry[type])),
max: Math.max(...entries.map((entry) => entry[type])),
avg: Math.round(entries.reduce((a, b) => a + b[type], 0) / entries.length)
diff --git a/server/util/loadCli.js b/server/util/loadCli.js
index 59bf383c..ae5a6763 100644
--- a/server/util/loadCli.js
+++ b/server/util/loadCli.js
@@ -1,9 +1,9 @@
-const libreProvider = require('./providers/loadLibre');
-const ooklaProvider = require('./providers/loadOokla');
-const cloudflareProvider = require('./providers/loadCloudflare');
+import * as libreProvider from './providers/loadLibre.js';
+import * as ooklaProvider from './providers/loadOokla.js';
+import * as cloudflareProvider from './providers/loadCloudflare.js';
-module.exports.load = async () => {
+export const load = async () => {
await libreProvider.load();
await ooklaProvider.load();
await cloudflareProvider.load();
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/server/util/loadInterfaces.js b/server/util/loadInterfaces.js
index b4c4b2ec..0f896a02 100644
--- a/server/util/loadInterfaces.js
+++ b/server/util/loadInterfaces.js
@@ -1,12 +1,12 @@
-const os = require('os');
-const https = require('https');
-const config = require('../controller/config');
+import os from 'node:os';
+import https from 'node:https';
+import * as config from '../controller/config.js';
-let usableInterfaces = {};
+export let interfaces = {};
-const requestInterfaces = async () => {
+export const requestInterfaces = async () => {
let interfacesNode = os.networkInterfaces();
- let interfaces = {};
+ let interfacesResult = {};
console.log("Looking for network interfaces...");
for (let i in interfacesNode) {
@@ -24,8 +24,8 @@ const requestInterfaces = async () => {
await new Promise((resolve) => {
const req = https.request(options, () => {
- if (!interfaces[i]) interfaces[i] = [];
- interfaces[i].push(address.address);
+ if (!interfacesResult[i]) interfacesResult[i] = [];
+ interfacesResult[i].push(address.address);
req.destroy();
resolve();
});
@@ -37,35 +37,32 @@ const requestInterfaces = async () => {
});
}
- if (!interfaces[i]) delete interfaces[i];
+ if (!interfacesResult[i]) delete interfacesResult[i];
}
- for (let i in interfaces) {
- for (let j in interfaces[i]) {
- if (interfaces[i][j].includes(".")) {
- usableInterfaces[i] = interfaces[i][j];
+ for (let i in interfacesResult) {
+ for (let j in interfacesResult[i]) {
+ if (interfacesResult[i][j].includes(".")) {
+ interfaces[i] = interfacesResult[i][j];
break;
}
}
- if (!usableInterfaces[i]) usableInterfaces[i] = interfaces[i][0];
+ if (!interfaces[i]) interfaces[i] = interfacesResult[i][0];
}
- for (let i in usableInterfaces) {
- console.log(`Found interface ${i} with IP ${usableInterfaces[i]}`);
+ for (let i in interfaces) {
+ console.log(`Found interface ${i} with IP ${interfaces[i]}`);
}
const currentInterface = await config.getValue("interface");
- if (!usableInterfaces[currentInterface]) {
+ if (!interfaces[currentInterface]) {
if (!currentInterface) {
console.warn("No interface set. Falling back to default.");
} else {
console.warn(`Interface ${currentInterface} not found. Falling back to default.`);
}
- await config.updateValue("interface", Object.keys(usableInterfaces)[0]);
+ await config.updateValue("interface", Object.keys(interfaces)[0]);
}
-}
-
-module.exports.requestInterfaces = requestInterfaces;
-module.exports.interfaces = usableInterfaces;
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/server/util/loadServers.js b/server/util/loadServers.js
index 8a3e6e30..68f1ea49 100644
--- a/server/util/loadServers.js
+++ b/server/util/loadServers.js
@@ -1,5 +1,5 @@
-const axios = require('axios');
-const fs = require('fs');
+import axios from 'axios';
+import fs from 'node:fs';
// Load servers from ookla
if (!fs.existsSync("data/servers/ookla.json")) {
diff --git a/server/util/providers/loadCloudflare.js b/server/util/providers/loadCloudflare.js
index 1a60b522..5a5621eb 100644
--- a/server/util/providers/loadCloudflare.js
+++ b/server/util/providers/loadCloudflare.js
@@ -1,20 +1,20 @@
-const fs = require('fs');
-const path = require('path');
-const { get } = require('https');
-const decompress = require("decompress");
-const decompressTarGz = require('decompress-targz');
-const decompressUnzip = require('decompress-unzip');
-const { file } = require("tmp");
-const binaries = require('../../config/binaries');
+import fs from 'node:fs';
+import path from 'node:path';
+import { get } from 'node:https';
+import decompress from "decompress";
+import decompressTarGz from 'decompress-targz';
+import decompressUnzip from 'decompress-unzip';
+import { file } from "tmp";
+import { cloudflareVersion, cloudflareList } from '../../config/binaries.js';
const binaryName = `cfspeedtest${process.platform === "win32" ? ".exe" : ""}`;
-const binaryDirectory = path.join(__dirname, "../../../bin");
+const binaryDirectory = path.join(process.cwd(), "bin");
const binaryPath = path.join(binaryDirectory, binaryName);
-const downloadBaseURL = `https://github.com/code-inflation/cfspeedtest/releases/download/v${binaries.cloudflareVersion}/`;
+const downloadBaseURL = `https://github.com/code-inflation/cfspeedtest/releases/download/v${cloudflareVersion}/`;
const binaryRegex = /cfspeedtest(.exe)?$/;
-module.exports.fileExists = async () => fs.existsSync(binaryPath);
+export const fileExists = async () => fs.existsSync(binaryPath);
const downloadToFile = (url, destinationPath) => {
return new Promise((resolve, reject) => {
@@ -31,7 +31,7 @@ const downloadToFile = (url, destinationPath) => {
res.on('error', reject);
}).on('error', reject);
});
-}
+};
const decompressBinary = async (archivePath) => {
await decompress(archivePath, binaryDirectory, {
@@ -42,13 +42,13 @@ const decompressBinary = async (archivePath) => {
return file;
}
});
-}
+};
-module.exports.downloadFile = async () => {
- let binary = binaries.cloudflareList.find(b => b.os === process.platform && b.arch === process.arch);
+export const downloadFile = async () => {
+ let binary = cloudflareList.find(b => b.os === process.platform && b.arch === process.arch);
if (!binary && process.platform === 'darwin') {
- binary = binaries.cloudflareList.find(b => b.os === 'darwin' && b.arch === 'universal');
+ binary = cloudflareList.find(b => b.os === 'darwin' && b.arch === 'universal');
}
if (!binary) {
@@ -71,8 +71,8 @@ module.exports.downloadFile = async () => {
});
};
-module.exports.load = async () => {
- if (!await module.exports.fileExists()) {
- await module.exports.downloadFile();
+export const load = async () => {
+ if (!await fileExists()) {
+ await downloadFile();
}
};
\ No newline at end of file
diff --git a/server/util/providers/loadLibre.js b/server/util/providers/loadLibre.js
index 68bad02f..3edbfff4 100644
--- a/server/util/providers/loadLibre.js
+++ b/server/util/providers/loadLibre.js
@@ -1,34 +1,35 @@
-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');
+import fs from 'node:fs';
+import { get } from 'node:https';
+import decompress from "decompress";
+import { file } from "tmp";
+import decompressTarGz from 'decompress-targz';
+import decompressUnzip from 'decompress-unzip';
+import { libreVersion, libreList } from '../../config/binaries.js';
+import path from 'node:path';
const binaryRegex = /librespeed-cli(.exe)?$/;
-const binaryDirectory = __dirname + "/../../../bin/";
-const binaryPath = `${binaryDirectory}/librespeed-cli` + (process.platform === "win32" ? ".exe" : "");
+const binaryDirectory = path.join(process.cwd(), "bin");
+const binaryPath = path.join(binaryDirectory, "librespeed-cli" + (process.platform === "win32" ? ".exe" : ""));
-const downloadPath = `https://github.com/librespeed/speedtest-cli/releases/download/v${binaries.libreVersion}/librespeed-cli_${binaries.libreVersion}_`;
+const downloadPath = `https://github.com/librespeed/speedtest-cli/releases/download/v${libreVersion}/librespeed-cli_${libreVersion}_`;
-module.exports.fileExists = async () => fs.existsSync(binaryPath);
+export const fileExists = async () => fs.existsSync(binaryPath);
-module.exports.downloadFile = async () => {
- const binary = binaries.libreList.find(b => b.os === process.platform && b.arch === process.arch);
+export const downloadFile = async () => {
+ const binary = 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) => {
+ file({postfix: binary.suffix}, async (err, tmpPath) => {
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, {
+ resp.pipe(fs.createWriteStream(tmpPath)).on('finish', async () => {
+ await decompress(tmpPath, binaryDirectory, {
plugins: [decompressTarGz(), decompressUnzip()],
filter: file => binaryRegex.test(file.path),
map: file => {
@@ -41,9 +42,9 @@ module.exports.downloadFile = async () => {
});
});
});
-}
+};
-module.exports.load = async () => {
- if (!await this.fileExists())
- await this.downloadFile();
-}
\ No newline at end of file
+export const load = async () => {
+ if (!await fileExists())
+ await downloadFile();
+};
\ No newline at end of file
diff --git a/server/util/providers/loadOokla.js b/server/util/providers/loadOokla.js
index a34dc9eb..d27ff097 100644
--- a/server/util/providers/loadOokla.js
+++ b/server/util/providers/loadOokla.js
@@ -1,30 +1,31 @@
-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');
+import fs from 'node:fs';
+import { get } from 'node:https';
+import decompress from "decompress";
+import { file } from "tmp";
+import decompressTarGz from 'decompress-targz';
+import decompressUnzip from 'decompress-unzip';
+import { ooklaVersion, ooklaList } from '../../config/binaries.js';
+import path from 'node:path';
const binaryRegex = /speedtest(.exe)?$/;
-const binaryDirectory = __dirname + "/../../../bin/";
-const binaryPath = `${binaryDirectory}/ookla` + (process.platform === "win32" ? ".exe" : "");
+const binaryDirectory = path.join(process.cwd(), "bin");
+const binaryPath = path.join(binaryDirectory, "speedtest" + (process.platform === "win32" ? ".exe" : ""));
-const downloadPath = `https://install.speedtest.net/app/cli/ookla-speedtest-${binaries.ooklaVersion}-`;
+const downloadPath = `https://install.speedtest.net/app/cli/ookla-speedtest-${ooklaVersion}-`;
-module.exports.fileExists = async () => fs.existsSync(binaryPath);
+export const fileExists = async () => fs.existsSync(binaryPath);
-module.exports.downloadFile = async () => {
- const binary = binaries.ooklaList.find(b => b.os === process.platform && b.arch === process.arch);
+export const downloadFile = async () => {
+ const binary = 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) => {
+ file({postfix: binary.suffix}, async (err, tmpPath) => {
get(downloadPath + binary.suffix, async resp => {
- resp.pipe(fs.createWriteStream(path)).on('finish', async () => {
- await decompress(path, binaryDirectory, {
+ resp.pipe(fs.createWriteStream(tmpPath)).on('finish', async () => {
+ await decompress(tmpPath, binaryDirectory, {
plugins: [decompressTarGz(), decompressUnzip()],
filter: file => binaryRegex.test(file.path),
map: file => {
@@ -37,9 +38,9 @@ module.exports.downloadFile = async () => {
});
});
});
-}
+};
-module.exports.load = async () => {
- if (!await this.fileExists())
- await this.downloadFile();
-}
\ No newline at end of file
+export const load = async () => {
+ if (!await fileExists())
+ await downloadFile();
+};
\ No newline at end of file
diff --git a/server/util/providers/parseData.js b/server/util/providers/parseData.js
index 13aaae55..ed7ce0b9 100644
--- a/server/util/providers/parseData.js
+++ b/server/util/providers/parseData.js
@@ -1,6 +1,6 @@
const roundSpeed = (bandwidth) => {
return Math.round(bandwidth / 1250) / 100;
-}
+};
const calculateJitter = (latencyMeasurements) => {
if (!latencyMeasurements || latencyMeasurements.length < 2) return null;
@@ -9,9 +9,9 @@ const calculateJitter = (latencyMeasurements) => {
totalDiff += Math.abs(latencyMeasurements[i] - latencyMeasurements[i - 1]);
}
return parseFloat((totalDiff / (latencyMeasurements.length - 1)).toFixed(2));
-}
+};
-module.exports.parseOokla = (test) => {
+export const parseOokla = (test) => {
let ping = Math.round(test.ping.latency);
let jitter = test.ping.jitter ? parseFloat(test.ping.jitter.toFixed(2)) : null;
let download = roundSpeed(test.download.bandwidth);
@@ -19,13 +19,13 @@ module.exports.parseOokla = (test) => {
let time = Math.round((test.download.elapsed + test.upload.elapsed) / 1000);
return {ping, jitter, download, upload, time, resultId: test.result?.id};
-}
+};
-module.exports.parseLibre = (test) => ({...test, ping: Math.round(test.ping),
+export const parseLibre = (test) => ({...test, ping: Math.round(test.ping),
jitter: test.jitter ? parseFloat(parseFloat(test.jitter).toFixed(2)) : null,
time: Math.round(test.elapsed / 1000), resultId: null});
-module.exports.parseCloudflare = (test) => {
+export const parseCloudflare = (test) => {
if (test && test.latency_measurement && test.speed_measurements) {
const downloadTests = test.speed_measurements.filter(t => t.test_type === "Download");
const uploadTests = test.speed_measurements.filter(t => t.test_type === "Upload");
@@ -48,15 +48,15 @@ module.exports.parseCloudflare = (test) => {
return {ping: 0, jitter: null, download: 0, upload: 0, time: 0, resultId: null};
};
-module.exports.parseData = (provider, data) => {
+export const parseData = (provider, data) => {
switch (provider) {
case "ookla":
- return this.parseOokla(data);
+ return parseOokla(data);
case "libre":
- return this.parseLibre(data);
+ return parseLibre(data);
case "cloudflare":
- return this.parseCloudflare(data);
+ return parseCloudflare(data);
default:
throw {message: "Invalid provider"};
}
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/server/util/speedtest.js b/server/util/speedtest.js
index 53cf13c7..dd8e961b 100644
--- a/server/util/speedtest.js
+++ b/server/util/speedtest.js
@@ -1,18 +1,18 @@
-const {spawn} = require('child_process');
-const interfaces = require('../util/loadInterfaces');
-const config = require('../controller/config');
-const fs = require('fs');
-const path = require('path');
+import { spawn } from 'node:child_process';
+import * as interfacesModule from '../util/loadInterfaces.js';
+import * as config from '../controller/config.js';
+import fs from 'node:fs';
+import path from 'node:path';
-module.exports = async (mode, serverId, serverUrl) => {
+export default async (mode, serverId, serverUrl) => {
const binaryPath = mode === "ookla" ? './bin/speedtest' + (process.platform === "win32" ? ".exe" : "")
: mode === "libre" ? './bin/librespeed-cli' + (process.platform === "win32" ? ".exe" : "")
: './bin/cfspeedtest' + (process.platform === "win32" ? ".exe" : "");
- if (!interfaces.interfaces) throw new Error("No interfaces found");
+ if (!interfacesModule.interfaces) throw new Error("No interfaces found");
const currentInterface = await config.getValue("interface");
- const interfaceIp = interfaces.interfaces[currentInterface];
+ const interfaceIp = interfacesModule.interfaces[currentInterface];
const startTime = new Date().getTime();
let args;