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] + + + + +

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;