diff --git a/.eslintrc.js b/.eslintrc.js index 9a63b414b..2d2d84042 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -119,5 +119,12 @@ module.exports = { eqeqeq: "off", }, }, + // Backend: no `any` + { + files: ["server/**/*.ts"], + rules: { + "@typescript-eslint/no-explicit-any": "error", + }, + }, ], }; diff --git a/extra/precompile-server.js b/extra/precompile-server.js index 31bc5b914..72c7e685c 100644 --- a/extra/precompile-server.js +++ b/extra/precompile-server.js @@ -7,7 +7,7 @@ const path = require("path"); const esbuild = require("esbuild"); const root = path.resolve(__dirname, ".."); -const esmEntries = ["server/server.js", "server/util-server.js"]; +const esmEntries = ["server/server.js"]; /** * Collect every .ts file below a directory. diff --git a/extra/run-backend-tests.mjs b/extra/run-backend-tests.mjs index 6c607b254..6f533ebb3 100644 --- a/extra/run-backend-tests.mjs +++ b/extra/run-backend-tests.mjs @@ -8,7 +8,7 @@ const PATTERNS = [ "test/frontend-test/**/*.js", ]; const CONTAINER_MARKER = "testcontainers"; -const NODE_ARGS = ["--import=tsx", "--test"]; +const NODE_ARGS = ["--import=tsx", "--import=./test/setup.ts", "--test"]; const extraArgs = process.argv.slice(2); const files = PATTERNS.flatMap((pattern) => globSync(pattern, { exclude: ["**/node_modules/**"] })).sort(); diff --git a/server/auth.ts b/server/auth.ts index b413523bb..608675d02 100644 --- a/server/auth.ts +++ b/server/auth.ts @@ -1,6 +1,7 @@ import expressBasicAuth from "express-basic-auth"; import dayjs from "dayjs"; import { R } from "redbean-node"; +import type { Bean } from "redbean-node"; import type express from "express"; import { log } from "../src/util"; import { loginRateLimiter, apiRateLimiter } from "./rate-limiter"; @@ -28,7 +29,7 @@ export async function login(username: string, password: string) { return null; } - const user: any = await R.findOne("user", "TRIM(username) = ? AND active = 1 ", [username.trim()]); + const user: Bean | null = await R.findOne("user", "TRIM(username) = ? AND active = 1 ", [username.trim()]); if (user && passwordHash.verify(password, user.password)) { // Upgrade the hash to bcrypt @@ -77,7 +78,7 @@ async function verifyLegacyAPIKey(key: string) { return false; } - const hash: any = await R.findOne("api_key", " id=? ", [index]); + const hash: Bean | null = await R.findOne("api_key", " id=? ", [index]); if (hash === null) { return false; diff --git a/server/better-auth.ts b/server/better-auth.ts index d8c960eeb..68461ded2 100644 --- a/server/better-auth.ts +++ b/server/better-auth.ts @@ -441,7 +441,7 @@ export async function checkPassword(username: string, password: string): Promise const { adapter, password: passwordVerifier } = await auth().$context; const internalAdapter = await authInternal(); - const user = await adapter.findOne>({ + const user = await adapter.findOne>({ model: "user", where: [{ field: "username", value: username }], }); diff --git a/server/check-version.js b/server/check-version.js index 79dfa10af..741eb076a 100644 --- a/server/check-version.js +++ b/server/check-version.js @@ -1,4 +1,4 @@ -const { setSetting, setting } = require("./util-server"); +const { setSetting, setting } = require("./util"); const axios = require("axios"); const compareVersions = require("compare-versions"); const { log } = require("../src/util"); diff --git a/server/docker.js b/server/docker.js index bd7c5e116..8107ca6fc 100644 --- a/server/docker.js +++ b/server/docker.js @@ -4,7 +4,7 @@ const https = require("https"); const fsAsync = require("fs").promises; const path = require("path"); const Database = require("./database"); -const { axiosAbortSignal, fsExists } = require("./util-server"); +const { axiosAbortSignal, fsExists } = require("./util"); class DockerHost { static CertificateFileNameCA = "ca.pem"; diff --git a/server/heartbeat-emit.ts b/server/heartbeat-emit.ts index 97d2b7839..fee691b5d 100644 --- a/server/heartbeat-emit.ts +++ b/server/heartbeat-emit.ts @@ -1,4 +1,4 @@ -import { getTotalClientInRoom } from "./util-server"; +import { getTotalClientInRoom } from "./util"; /** * Room every logged-in socket joins; v3 has a single dashboard. diff --git a/server/model/domain_expiry.js b/server/model/domain_expiry.js index 6e4fcf56c..a160a2214 100644 --- a/server/model/domain_expiry.js +++ b/server/model/domain_expiry.js @@ -2,7 +2,7 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); const { R } = require("redbean-node"); const { log, TYPES_WITH_DOMAIN_EXPIRY_SUPPORT_VIA_FIELD } = require("../../src/util"); const { parse: parseTld } = require("tldts"); -const { setting, setSetting } = require("../util-server"); +const { setting, setSetting } = require("../util"); const { Notification } = require("../notification"); const TranslatableError = require("../translatable-error"); const dayjs = require("dayjs"); diff --git a/server/model/monitor.js b/server/model/monitor.js index 09b86c1ae..ea0c75ebf 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -38,7 +38,7 @@ const { axiosAbortSignal, encodeBase64, checkCertExpiryNotifications, -} = require("../util-server"); +} = require("../util"); const { R } = require("redbean-node"); const { BeanModel } = require("redbean-node/dist/bean-model"); const { Notification } = require("../notification"); diff --git a/server/model/status_page.js b/server/model/status_page.js index 66728d39e..6d3a400a1 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -9,7 +9,7 @@ const { Feed } = require("feed"); const config = require("../config"); const dayjs = require("dayjs"); -const { setting } = require("../util-server"); +const { setting } = require("../util"); const { STATUS_PAGE_ALL_DOWN, STATUS_PAGE_ALL_UP, diff --git a/server/modules/dayjs/plugin/timezone.d.ts b/server/modules/dayjs/plugin/timezone.d.ts deleted file mode 100644 index 4d98fc7f4..000000000 --- a/server/modules/dayjs/plugin/timezone.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { PluginFunc, ConfigType } from "dayjs"; - -declare const plugin: PluginFunc; -export = plugin; - -declare module "dayjs" { - interface Dayjs { - tz(timezone?: string, keepLocalTime?: boolean): Dayjs; - offsetName(type?: "short" | "long"): string | undefined; - } - - interface DayjsTimezone { - (date: ConfigType, timezone?: string): Dayjs; - (date: ConfigType, format: string, timezone?: string): Dayjs; - guess(): string; - setDefault(timezone?: string): void; - } - - const tz: DayjsTimezone; -} diff --git a/server/modules/dayjs/plugin/timezone.js b/server/modules/dayjs/plugin/timezone.js deleted file mode 100644 index b973ece52..000000000 --- a/server/modules/dayjs/plugin/timezone.js +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Copy from node_modules/dayjs/plugin/timezone.js - * Try to fix https://github.com/louislam/uptime-kuma/issues/2318 - * Source: https://github.com/iamkun/dayjs/tree/dev/src/plugin/utc - * License: MIT - */ -!(function (t, e) { - // eslint-disable-next-line no-undef - typeof exports == "object" && typeof module != "undefined" - ? (module.exports = e()) - : typeof define == "function" && define.amd - ? define(e) - : ((t = typeof globalThis != "undefined" ? globalThis : t || self).dayjs_plugin_timezone = e()); -})(this, function () { - "use strict"; - let t = { - year: 0, - month: 1, - day: 2, - hour: 3, - minute: 4, - second: 5, - }; - let e = {}; - return function (n, i, o) { - let r; - let a = function (t, n, i) { - void 0 === i && (i = {}); - let o = new Date(t); - let r = (function (t, n) { - void 0 === n && (n = {}); - let i = n.timeZoneName || "short"; - let o = t + "|" + i; - let r = e[o]; - return ( - r || - ((r = new Intl.DateTimeFormat("en-US", { - hour12: !1, - timeZone: t, - year: "numeric", - month: "2-digit", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - timeZoneName: i, - })), - (e[o] = r)), - r - ); - })(n, i); - return r.formatToParts(o); - }; - let u = function (e, n) { - let i = a(e, n); - let r = []; - let u = 0; - for (; u < i.length; u += 1) { - let f = i[u]; - let s = f.type; - let m = f.value; - let c = t[s]; - c >= 0 && (r[c] = parseInt(m, 10)); - } - let d = r[3]; - let l = d === 24 ? 0 : d; - let v = r[0] + "-" + r[1] + "-" + r[2] + " " + l + ":" + r[4] + ":" + r[5] + ":000"; - let h = +e; - return (o.utc(v).valueOf() - (h -= h % 1e3)) / 6e4; - }; - let f = i.prototype; - ((f.tz = function (t, e) { - void 0 === t && (t = r); - let n = this.utcOffset(); - let i = this.toDate(); - let a = i.toLocaleString("en-US", { timeZone: t }).replace("\u202f", " "); - let u = Math.round((i - new Date(a)) / 1e3 / 60); - let f = o(a) - .$set("millisecond", this.$ms) - .utcOffset(15 * -Math.round(i.getTimezoneOffset() / 15) - u, !0); - if (e) { - let s = f.utcOffset(); - f = f.add(n - s, "minute"); - } - return ((f.$x.$timezone = t), f); - }), - (f.offsetName = function (t) { - let e = this.$x.$timezone || o.tz.guess(); - let n = a(this.valueOf(), e, { timeZoneName: t }).find(function (t) { - return t.type.toLowerCase() === "timezonename"; - }); - return n && n.value; - })); - let s = f.startOf; - ((f.startOf = function (t, e) { - if (!this.$x || !this.$x.$timezone) { - return s.call(this, t, e); - } - let n = o(this.format("YYYY-MM-DD HH:mm:ss:SSS")); - return s.call(n, t, e).tz(this.$x.$timezone, !0); - }), - (o.tz = function (t, e, n) { - let i = n && e; - let a = n || e || r; - let f = u(+o(), a); - if (typeof t != "string") { - return o(t).tz(a); - } - let s = (function (t, e, n) { - let i = t - 60 * e * 1e3; - let o = u(i, n); - if (e === o) { - return [i, e]; - } - let r = u((i -= 60 * (o - e) * 1e3), n); - return o === r ? [i, o] : [t - 60 * Math.min(o, r) * 1e3, Math.max(o, r)]; - })(o.utc(t, i).valueOf(), f, a); - let m = s[0]; - let c = s[1]; - let d = o(m).utcOffset(c); - return ((d.$x.$timezone = a), d); - }), - (o.tz.guess = function () { - return Intl.DateTimeFormat().resolvedOptions().timeZone; - }), - (o.tz.setDefault = function (t) { - r = t; - })); - }; -}); diff --git a/server/monitor-tls.ts b/server/monitor-tls.ts index e1228aa2a..dabddb469 100644 --- a/server/monitor-tls.ts +++ b/server/monitor-tls.ts @@ -1,4 +1,4 @@ -import { checkCertificate, checkCertificateHostname } from "./util-server"; +import { checkCertificate, checkCertificateHostname } from "./util"; /** Minimal TLS socket shape needed for the certificate info */ export interface TlsSocketLike { diff --git a/server/monitor-types/globalping.js b/server/monitor-types/globalping.js index 5b235d7d9..0c463654e 100644 --- a/server/monitor-types/globalping.js +++ b/server/monitor-types/globalping.js @@ -8,7 +8,7 @@ const { encodeBase64, getDaysRemaining, checkCertExpiryNotifications, -} = require("../util-server"); +} = require("../util"); const { R } = require("redbean-node"); /** diff --git a/server/monitor-types/rabbitmq.js b/server/monitor-types/rabbitmq.js index bd07e9918..2d35ab25d 100644 --- a/server/monitor-types/rabbitmq.js +++ b/server/monitor-types/rabbitmq.js @@ -1,6 +1,6 @@ const { MonitorType } = require("./monitor-type"); const { log, UP } = require("../../src/util"); -const { axiosAbortSignal } = require("../util-server"); +const { axiosAbortSignal } = require("../util"); const axios = require("axios"); class RabbitMqMonitorType extends MonitorType { diff --git a/server/monitor-types/real-browser-monitor-type.js b/server/monitor-types/real-browser-monitor-type.js index e2d4ef9fe..da2ad043b 100644 --- a/server/monitor-types/real-browser-monitor-type.js +++ b/server/monitor-types/real-browser-monitor-type.js @@ -7,7 +7,7 @@ const path = require("path"); const Database = require("../database"); const config = require("../config"); const { RemoteBrowser } = require("../remote-browser"); -const { commandExists } = require("../util-server"); +const { commandExists } = require("../util"); /** * Cached instance of a browser diff --git a/server/monitor-types/steam.js b/server/monitor-types/steam.js index 5eaad8c1c..86342f315 100644 --- a/server/monitor-types/steam.js +++ b/server/monitor-types/steam.js @@ -6,7 +6,7 @@ const { PING_PER_REQUEST_TIMEOUT_DEFAULT, } = require("../../src/util"); const { Settings } = require("../settings"); -const { checkStatusCode } = require("../util-server"); +const { checkStatusCode } = require("../util"); const { ping } = require("./ping"); const axios = require("axios"); const crypto = require("crypto"); diff --git a/server/monitor-types/websocket-upgrade.js b/server/monitor-types/websocket-upgrade.js index 97ef77b60..d91d48d0f 100644 --- a/server/monitor-types/websocket-upgrade.js +++ b/server/monitor-types/websocket-upgrade.js @@ -1,7 +1,7 @@ const { MonitorType } = require("./monitor-type"); const WebSocket = require("ws"); const { UP } = require("../../src/util"); -const { checkStatusCode, getOidcTokenClientCredentials } = require("../util-server"); +const { checkStatusCode, getOidcTokenClientCredentials } = require("../util"); // Define closing error codes https://www.iana.org/assignments/websocket/websocket.xml#close-code-number const WS_ERR_CODE = { 1002: "Protocol error", diff --git a/server/notification-providers/Webpush.js b/server/notification-providers/Webpush.js index 86b94c325..c4564d9b2 100644 --- a/server/notification-providers/Webpush.js +++ b/server/notification-providers/Webpush.js @@ -1,6 +1,6 @@ const NotificationProvider = require("./notification-provider"); const webpush = require("web-push"); -const { setting } = require("../util-server"); +const { setting } = require("../util"); class Webpush extends NotificationProvider { name = "Webpush"; diff --git a/server/notification-providers/heii-oncall.js b/server/notification-providers/heii-oncall.js index 1e66f0d72..b925fa8d9 100644 --- a/server/notification-providers/heii-oncall.js +++ b/server/notification-providers/heii-oncall.js @@ -1,5 +1,5 @@ const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util"); -const { setting } = require("../util-server"); +const { setting } = require("../util"); const NotificationProvider = require("./notification-provider"); const axios = require("axios"); diff --git a/server/notification-providers/notifery.js b/server/notification-providers/notifery.js index 99cc7bed2..9c3b030ed 100644 --- a/server/notification-providers/notifery.js +++ b/server/notification-providers/notifery.js @@ -1,5 +1,5 @@ const { getMonitorRelativeURL, UP } = require("../../src/util"); -const { setting } = require("../util-server"); +const { setting } = require("../util"); const NotificationProvider = require("./notification-provider"); const axios = require("axios"); diff --git a/server/notification-providers/pushover.js b/server/notification-providers/pushover.js index 6d935a3a3..79cbcf384 100644 --- a/server/notification-providers/pushover.js +++ b/server/notification-providers/pushover.js @@ -1,5 +1,5 @@ const { getMonitorRelativeURL } = require("../../src/util"); -const { setting } = require("../util-server"); +const { setting } = require("../util"); const { UP } = require("../../src/util"); const NotificationProvider = require("./notification-provider"); diff --git a/server/notification-providers/slack.js b/server/notification-providers/slack.js index 32f06641c..25e31457d 100644 --- a/server/notification-providers/slack.js +++ b/server/notification-providers/slack.js @@ -1,6 +1,6 @@ const NotificationProvider = require("./notification-provider"); const axios = require("axios"); -const { setSettings, setting } = require("../util-server"); +const { setSettings, setting } = require("../util"); const { getMonitorRelativeURL, UP, log } = require("../../src/util"); const isUrl = require("is-url"); diff --git a/server/notification-providers/teams.js b/server/notification-providers/teams.js index 5fd93afe2..6d1e5364c 100644 --- a/server/notification-providers/teams.js +++ b/server/notification-providers/teams.js @@ -1,6 +1,6 @@ const NotificationProvider = require("./notification-provider"); const axios = require("axios"); -const { setting } = require("../util-server"); +const { setting } = require("../util"); const { DOWN, UP, getMonitorRelativeURL } = require("../../src/util"); class Teams extends NotificationProvider { diff --git a/server/notification.js b/server/notification.js index 4e5e69dc2..60d06444b 100644 --- a/server/notification.js +++ b/server/notification.js @@ -103,7 +103,7 @@ const AmootSMS = require("./notification-providers/amootsms"); const SMSPlanet = require("./notification-providers/sms-planet"); const SpugPush = require("./notification-providers/spugpush"); const SMSIR = require("./notification-providers/smsir"); -const { commandExists } = require("./util-server"); +const { commandExists } = require("./util"); const Whatsapp360messenger = require("./notification-providers/360messenger"); const Webpush = require("./notification-providers/Webpush"); const HaloPSA = require("./notification-providers/HaloPSA"); diff --git a/server/routers/api-router.js b/server/routers/api-router.js index 7b0882001..5dc0b73cd 100644 --- a/server/routers/api-router.js +++ b/server/routers/api-router.js @@ -1,11 +1,5 @@ let express = require("express"); -const { - allowDevAllOrigin, - allowAllOrigin, - percentageToColor, - filterAndJoin, - sendHttpError, -} = require("../util-server"); +const { allowDevAllOrigin, allowAllOrigin, percentageToColor, filterAndJoin, sendHttpError } = require("../util"); const { R } = require("redbean-node"); const apicache = require("../modules/apicache"); const Monitor = require("../model/monitor"); diff --git a/server/routers/better-auth-router.ts b/server/routers/better-auth-router.ts index dd67a06ef..8b8a79dee 100644 --- a/server/routers/better-auth-router.ts +++ b/server/routers/better-auth-router.ts @@ -5,8 +5,7 @@ import { R } from "redbean-node"; import { log } from "../../src/util"; // @ts-ignore -import { allowDevOrigin } from "../util-server.js"; -import { generalErrorResponse } from "../util2"; +import { allowDevOrigin, generalErrorResponse } from "../util"; let processingSetup = false; let _hasUser = false; diff --git a/server/routers/status-page-router.js b/server/routers/status-page-router.js index dea449c46..02e3f2ab2 100644 --- a/server/routers/status-page-router.js +++ b/server/routers/status-page-router.js @@ -2,7 +2,7 @@ let express = require("express"); const apicache = require("../modules/apicache"); const { UptimeKumaServer } = require("../uptime-kuma-server"); const StatusPage = require("../model/status_page"); -const { allowDevAllOrigin, sendHttpError } = require("../util-server"); +const { allowDevAllOrigin, sendHttpError } = require("../util"); const { R } = require("redbean-node"); const { badgeConstants } = require("../../src/util"); const { makeBadge } = require("badge-maker"); diff --git a/server/server.js b/server/server.js index 1c2eefbb8..baee91275 100644 --- a/server/server.js +++ b/server/server.js @@ -3,6 +3,16 @@ * node "server/server.js" * DO NOT require("./server") in other modules, it likely creates circular dependency! */ +import { + initDayjs, + getSettings, + setSettings, + setting, + checkLogin, + allowDevAllOrigin, + printServerUrls, + allowDevOrigin, +} from "./util"; import { getRandomInt, isDevEnv, log, sleep } from "../src/util"; import { auth, createHeaders, doubleCheckPassword, getDisableAuthSession, getSessionFromHeaders } from "./better-auth"; import { createBetterAuthRouter, needSetup } from "./routers/better-auth-router"; @@ -14,11 +24,7 @@ import * as fs from "node:fs"; console.log("Welcome to Uptime Kuma"); -// As the log function need to use dayjs, it should be very top -const dayjs = require("dayjs"); -dayjs.extend(require("dayjs/plugin/utc")); -dayjs.extend(require("./modules/dayjs/plugin/timezone")); -dayjs.extend(require("dayjs/plugin/customParseFormat")); +initDayjs(); // Load environment variables from `.env` try { @@ -119,15 +125,6 @@ log.debug("server", "Importing Monitor"); const Monitor = require("./model/monitor"); log.debug("server", "Importing Settings"); -const { - getSettings, - setSettings, - setting, - checkLogin, - allowDevAllOrigin, - printServerUrls, - allowDevOrigin, -} = require("./util-server"); log.debug("server", "Importing Notification"); const { Notification } = require("./notification"); diff --git a/server/setup-database.js b/server/setup-database.js index 42342576a..8bb3d24c8 100644 --- a/server/setup-database.js +++ b/server/setup-database.js @@ -4,7 +4,7 @@ const expressStaticGzip = require("express-static-gzip"); const fs = require("fs"); const path = require("path"); const Database = require("./database"); -const { allowDevAllOrigin, printServerUrls } = require("./util-server"); +const { allowDevAllOrigin, printServerUrls } = require("./util"); const mysql = require("mysql2/promise"); const { isSSL, sslKey, sslCert, sslKeyPassphrase } = require("./config"); const https = require("https"); diff --git a/server/socket-handlers/chart-socket-handler.js b/server/socket-handlers/chart-socket-handler.js index 5e1898e54..1b0443d3b 100644 --- a/server/socket-handlers/chart-socket-handler.js +++ b/server/socket-handlers/chart-socket-handler.js @@ -1,4 +1,4 @@ -const { checkLogin } = require("../util-server"); +const { checkLogin } = require("../util"); const { UptimeCalculator } = require("../uptime-calculator"); const { log } = require("../../src/util"); diff --git a/server/socket-handlers/cloudflared-socket-handler.js b/server/socket-handlers/cloudflared-socket-handler.js index b14a51bf0..2e67311c4 100644 --- a/server/socket-handlers/cloudflared-socket-handler.js +++ b/server/socket-handlers/cloudflared-socket-handler.js @@ -1,4 +1,4 @@ -const { checkLogin, setSetting, setting } = require("../util-server"); +const { checkLogin, setSetting, setting } = require("../util"); const { CloudflaredTunnel } = require("node-cloudflared-tunnel"); const { UptimeKumaServer } = require("../uptime-kuma-server"); const { log } = require("../../src/util"); diff --git a/server/socket-handlers/database-socket-handler.js b/server/socket-handlers/database-socket-handler.js index 14677e35f..cf7433958 100644 --- a/server/socket-handlers/database-socket-handler.js +++ b/server/socket-handlers/database-socket-handler.js @@ -1,4 +1,4 @@ -const { checkLogin } = require("../util-server"); +const { checkLogin } = require("../util"); const Database = require("../database"); /** diff --git a/server/socket-handlers/docker-socket-handler.js b/server/socket-handlers/docker-socket-handler.js index 160b6cd1f..1af5dfb06 100644 --- a/server/socket-handlers/docker-socket-handler.js +++ b/server/socket-handlers/docker-socket-handler.js @@ -1,5 +1,5 @@ const { sendDockerHostList } = require("../client"); -const { checkLogin } = require("../util-server"); +const { checkLogin } = require("../util"); const { DockerHost } = require("../docker"); const { log } = require("../../src/util"); diff --git a/server/socket-handlers/general-socket-handler.js b/server/socket-handlers/general-socket-handler.js index fbb76e656..506eeb806 100644 --- a/server/socket-handlers/general-socket-handler.js +++ b/server/socket-handlers/general-socket-handler.js @@ -1,7 +1,7 @@ const { log } = require("../../src/util"); const { Settings } = require("../settings"); const { sendInfo } = require("../client"); -const { checkLogin } = require("../util-server"); +const { checkLogin } = require("../util"); const { games } = require("gamedig"); const { testChrome } = require("../monitor-types/real-browser-monitor-type"); const { getPM2ProcessList } = require("../util/pm2"); diff --git a/server/socket-handlers/maintenance-socket-handler.js b/server/socket-handlers/maintenance-socket-handler.js index 457776c03..f989e3f78 100644 --- a/server/socket-handlers/maintenance-socket-handler.js +++ b/server/socket-handlers/maintenance-socket-handler.js @@ -1,4 +1,4 @@ -const { checkLogin } = require("../util-server"); +const { checkLogin } = require("../util"); const { log } = require("../../src/util"); const { R } = require("redbean-node"); const apicache = require("../modules/apicache"); diff --git a/server/socket-handlers/proxy-socket-handler.js b/server/socket-handlers/proxy-socket-handler.js index d83a19fbe..c1f185997 100644 --- a/server/socket-handlers/proxy-socket-handler.js +++ b/server/socket-handlers/proxy-socket-handler.js @@ -1,4 +1,4 @@ -const { checkLogin } = require("../util-server"); +const { checkLogin } = require("../util"); const { Proxy } = require("../proxy"); const { sendProxyList } = require("../client"); const { UptimeKumaServer } = require("../uptime-kuma-server"); diff --git a/server/socket-handlers/remote-browser-socket-handler.js b/server/socket-handlers/remote-browser-socket-handler.js index 0f0a25d95..30d8f750c 100644 --- a/server/socket-handlers/remote-browser-socket-handler.js +++ b/server/socket-handlers/remote-browser-socket-handler.js @@ -1,5 +1,5 @@ const { sendRemoteBrowserList } = require("../client"); -const { checkLogin } = require("../util-server"); +const { checkLogin } = require("../util"); const { RemoteBrowser } = require("../remote-browser"); const { log } = require("../../src/util"); diff --git a/server/socket-handlers/status-page-socket-handler.js b/server/socket-handlers/status-page-socket-handler.js index 9a1ce2b79..8102a67c0 100644 --- a/server/socket-handlers/status-page-socket-handler.js +++ b/server/socket-handlers/status-page-socket-handler.js @@ -1,5 +1,5 @@ const { R } = require("redbean-node"); -const { checkLogin } = require("../util-server"); +const { checkLogin } = require("../util"); const dayjs = require("dayjs"); const { log } = require("../../src/util"); const ImageDataURI = require("../image-data-uri"); diff --git a/server/util-server.js b/server/util.ts similarity index 69% rename from server/util-server.js rename to server/util.ts index 198bead38..eccf180e3 100644 --- a/server/util-server.js +++ b/server/util.ts @@ -1,4 +1,15 @@ +import dayjs from "dayjs"; +import customParseFormat from "dayjs/plugin/customParseFormat"; +import timezone from "dayjs/plugin/timezone"; +import utc from "dayjs/plugin/utc"; +import { ZodError } from "zod"; import { checkLogin as betterAuthCheckLogin } from "./better-auth"; +import type { Request, Response } from "express"; +import type { SASLOptions } from "kafkajs"; +import type { TokenSet } from "openid-client"; +import type { Bean } from "redbean-node"; +import type { Server, Socket } from "socket.io"; +import type { TLSSocket } from "tls"; const { R } = require("redbean-node"); const { log, badgeConstants } = require("../src/util"); @@ -16,32 +27,28 @@ const { rfc2865: { file, attributes }, }, } = require("node-radius-utils"); -const dayjs = require("dayjs"); -dayjs.extend(require("dayjs/plugin/utc")); -// SASLOptions used in JSDoc -// eslint-disable-next-line no-unused-vars -const { Kafka, SASLOptions } = require("kafkajs"); +const { Kafka } = require("kafkajs"); const crypto = require("crypto"); /** * Gets an Access Token from an oidc/oauth2 provider - * @param {string} tokenEndpoint The token URI from the auth service provider - * @param {string} clientId The oidc/oauth application client id - * @param {string} clientSecret The oidc/oauth application client secret - * @param {string} scope The scope(s) for which the token should be issued for - * @param {string} audience The audience for which the token should be issued for - * @param {string} authMethod The method used to send the credentials. Default client_secret_basic - * @returns {Promise} TokenSet promise if the token request was successful + * @param tokenEndpoint The token URI from the auth service provider + * @param clientId The oidc/oauth application client id + * @param clientSecret The oidc/oauth application client secret + * @param scope The scope(s) for which the token should be issued for + * @param audience The audience for which the token should be issued for + * @param authMethod The method used to send the credentials. Default client_secret_basic + * @returns TokenSet promise if the token request was successful */ -exports.getOidcTokenClientCredentials = async ( - tokenEndpoint, - clientId, - clientSecret, - scope, - audience, - authMethod = "client_secret_basic" -) => { +export async function getOidcTokenClientCredentials( + tokenEndpoint: string, + clientId: string, + clientSecret: string, + scope: string, + audience: string, + authMethod: string = "client_secret_basic" +): Promise { const oauthProvider = new oidc.Issuer({ token_endpoint: tokenEndpoint }); let client = new oauthProvider.Client({ client_id: clientId, @@ -62,23 +69,29 @@ exports.getOidcTokenClientCredentials = async ( grantParams.audience = audience; } return await client.grant(grantParams); -}; +} /** * Monitor Kafka using Producer - * @param {string[]} brokers List of kafka brokers to connect, host and + * @param brokers List of kafka brokers to connect, host and * port joined by ':' - * @param {string} topic Topic name to produce into - * @param {string} message Message to produce - * @param {object} options Kafka client options. Contains ssl, clientId, + * @param topic Topic name to produce into + * @param message Message to produce + * @param options Kafka client options. Contains ssl, clientId, * allowAutoTopicCreation and interval (interval defaults to 20, * allowAutoTopicCreation defaults to false, clientId defaults to * "Uptime-Kuma" and ssl defaults to false) - * @param {SASLOptions} saslOptions Options for kafka client + * @param saslOptions Options for kafka client * Authentication (SASL) (defaults to {}) - * @returns {Promise} Status message + * @returns Status message */ -exports.kafkaProducerAsync = function (brokers, topic, message, options = {}, saslOptions = {}) { +export function kafkaProducerAsync( + brokers: string[], + topic: string, + message: string, + options: object = {}, + saslOptions: SASLOptions = {} +): Promise { return new Promise((resolve, reject) => { const { interval = 20, @@ -168,15 +181,15 @@ exports.kafkaProducerAsync = function (brokers, topic, message, options = {}, sa } }); }); -}; +} /** * Use NTLM Auth for a http request. - * @param {object} options The http request options - * @param {object} ntlmOptions The auth options - * @returns {Promise<(string[] | object[] | object)>} NTLM response + * @param options The http request options + * @param ntlmOptions The auth options + * @returns NTLM response */ -exports.httpNtlm = function (options, ntlmOptions) { +export function httpNtlm(options: object, ntlmOptions: object): Promise { return new Promise((resolve, reject) => { let client = NtlmClient(ntlmOptions); @@ -188,30 +201,30 @@ exports.httpNtlm = function (options, ntlmOptions) { reject(err); }); }); -}; +} /** * Query radius server - * @param {string} hostname Hostname of radius server - * @param {string} username Username to use - * @param {string} password Password to use - * @param {string} calledStationId ID of called station - * @param {string} callingStationId ID of calling station - * @param {string} secret Secret to use - * @param {number} port Port to contact radius server on - * @param {number} timeout Timeout for connection to use - * @returns {Promise} Response from server + * @param hostname Hostname of radius server + * @param username Username to use + * @param password Password to use + * @param calledStationId ID of called station + * @param callingStationId ID of calling station + * @param secret Secret to use + * @param port Port to contact radius server on + * @param timeout Timeout for connection to use + * @returns Response from server */ -exports.radius = function ( - hostname, - username, - password, - calledStationId, - callingStationId, - secret, - port = 1812, - timeout = 2500 -) { +export function radius( + hostname: string, + username: string, + password: string, + calledStationId: string, + callingStationId: string, + secret: string, + port: number = 1812, + timeout: number = 2500 +): Promise<{ code: string }> { const client = new RadiusClient({ host: hostname, hostPort: port, @@ -247,47 +260,47 @@ exports.radius = function ( throw enhancedError; } }); -}; +} /** * Retrieve value of setting based on key - * @param {string} key Key of setting to retrieve - * @returns {Promise} Value + * @param key Key of setting to retrieve + * @returns Value * @deprecated Use await Settings.get(key) */ -exports.setting = async function (key) { +export async function setting(key: string): Promise { return await Settings.get(key); -}; +} /** * Sets the specified setting to specified value - * @param {string} key Key of setting to set - * @param {any} value Value to set to - * @param {?string} type Type of setting - * @returns {Promise} + * @param key Key of setting to set + * @param value Value to set to + * @param type Type of setting + * @returns */ -exports.setSetting = async function (key, value, type = null) { +export async function setSetting(key: string, value: unknown, type: string | null = null): Promise { await Settings.set(key, value, type); -}; +} /** * Get settings based on type - * @param {string} type The type of setting - * @returns {Promise} Settings of requested type + * @param type The type of setting + * @returns Settings of requested type */ -exports.getSettings = async function (type) { +export async function getSettings(type: string): Promise> { return await Settings.getSettings(type); -}; +} /** * Set settings based on type - * @param {string} type Type of settings to set - * @param {object} data Values of settings - * @returns {Promise} + * @param type Type of settings to set + * @param data Values of settings + * @returns */ -exports.setSettings = async function (type, data) { +export async function setSettings(type: string, data: object): Promise { await Settings.setSettings(type, data); -}; +} // ssl-checker by @dyaa //https://github.com/dyaa/ssl-checker/blob/master/src/index.ts @@ -302,18 +315,17 @@ const getDaysBetween = (validFrom, validTo) => Math.round(Math.abs(+validFrom - /** * Get days remaining from a time range - * @param {Date} validFrom Start date - * @param {Date} validTo End date - * @returns {number} Number of days remaining + * @param validFrom Start date + * @param validTo End date + * @returns Number of days remaining */ -const getDaysRemaining = (validFrom, validTo) => { +export function getDaysRemaining(validFrom: Date, validTo: Date): number { const daysRemaining = getDaysBetween(validFrom, validTo); if (new Date(validTo).getTime() < new Date().getTime()) { return -daysRemaining; } return daysRemaining; -}; -module.exports.getDaysRemaining = getDaysRemaining; +} /** * Fix certificate info for display @@ -366,10 +378,10 @@ const parseCertificateInfo = function (info) { /** * Check if certificate is valid - * @param {tls.TLSSocket} socket TLSSocket, which may or may not be connected - * @returns {null | {valid: boolean, certInfo: object}} Object containing certificate information + * @param socket TLSSocket, which may or may not be connected + * @returns} Object containing certificate information */ -exports.checkCertificate = function (socket) { +export function checkCertificate(socket: TLSSocket): null | { valid: boolean; certInfo: object } { let certInfoStartTime = dayjs().valueOf(); // Return null if there is no socket @@ -391,16 +403,16 @@ exports.checkCertificate = function (socket) { valid: valid, certInfo: parsedInfo, }; -}; +} /** * Checks if the certificate is valid for the provided hostname. * Defaults to true if feature `X509Certificate` is not available, or input is not valid. - * @param {Buffer} certBuffer - The certificate buffer. - * @param {string} hostname - The hostname to compare against. - * @returns {boolean} True if the certificate is valid for the provided hostname, false otherwise. + * @param certBuffer - The certificate buffer. + * @param hostname - The hostname to compare against. + * @returns True if the certificate is valid for the provided hostname, false otherwise. */ -exports.checkCertificateHostname = function (certBuffer, hostname) { +export function checkCertificateHostname(certBuffer: Buffer, hostname: string): boolean { let X509Certificate; try { X509Certificate = require("node:crypto").X509Certificate; @@ -415,15 +427,15 @@ exports.checkCertificateHostname = function (certBuffer, hostname) { let certObject = new X509Certificate(certBuffer); return certObject.checkHost(hostname) !== undefined; -}; +} /** * Check if the provided status code is within the accepted ranges - * @param {number} status The status code to check - * @param {string[]} acceptedCodes An array of accepted status codes - * @returns {boolean} True if status code within range, false otherwise + * @param status The status code to check + * @param acceptedCodes An array of accepted status codes + * @returns True if status code within range, false otherwise */ -exports.checkStatusCode = function (status, acceptedCodes) { +export function checkStatusCode(status: number, acceptedCodes: string[]): boolean { if (acceptedCodes == null || acceptedCodes.length === 0) { return false; } @@ -450,15 +462,15 @@ exports.checkStatusCode = function (status, acceptedCodes) { } return false; -}; +} /** * Get total number of clients in room - * @param {Server} io Socket server instance - * @param {string} roomName Name of room to check - * @returns {number} Total clients in room + * @param io Socket server instance + * @param roomName Name of room to check + * @returns Total clients in room */ -exports.getTotalClientInRoom = (io, roomName) => { +export function getTotalClientInRoom(io: Server, roomName: string): number { const sockets = io.sockets; if (!sockets) { @@ -478,104 +490,104 @@ exports.getTotalClientInRoom = (io, roomName) => { } else { return 0; } -}; +} /** * @deprecated Use allowDevOrigin * Allow CORS all origins if development - * @param {object} res Response object from axios - * @returns {void} + * @param res Response object from axios + * @returns */ -exports.allowDevAllOrigin = (res) => { +export function allowDevAllOrigin(res: object): void { if (process.env.NODE_ENV === "development") { - exports.allowAllOrigin(res); + allowAllOrigin(res); } -}; +} /** * @deprecated Use allowOrigin * Allow CORS all origins - * @param {object} res Response object from axios - * @returns {void} + * @param res Response object from axios + * @returns */ -exports.allowAllOrigin = (res) => { +export function allowAllOrigin(res: object): void { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); -}; +} /** * Allow CORS all origins if development - * @param {Request} req Express request object - * @param {Response} res Express response object - * @returns {void} + * @param req Express request object + * @param res Express response object + * @returns */ -exports.allowDevOrigin = (req, res) => { +export function allowDevOrigin(req: Request, res: Response): void { if (process.env.NODE_ENV === "development") { - exports.allowOrigin(req, res); + allowOrigin(req, res); } -}; +} /** * Allow CORS all origins * Since Allow-Credentials is set to true, the Access-Control-Allow-Origin cannot be *, so we will set it to the request origin. - * @param {Request} req Express request object - * @param {Response} res Response object - * @returns {void} + * @param req Express request object + * @param res Response object + * @returns */ -exports.allowOrigin = (req, res) => { +export function allowOrigin(req: Request, res: Response): void { res.header("Access-Control-Allow-Origin", req.get("origin")); res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); res.header("Access-Control-Allow-Credentials", "true"); -}; +} /** * @deprecated Use better-auth's checkLogin * Check if a user is logged in - * @param {Socket} socket Socket instance - * @returns {void} + * @param socket Socket instance + * @returns * @throws The user is not logged in */ -exports.checkLogin = (socket) => { +export function checkLogin(socket: Socket): void { betterAuthCheckLogin(socket); -}; +} /** * Returns a color code in hex format based on a given percentage: * 0% => hue = 10 => red * 100% => hue = 90 => green - * @param {number} percentage float, 0 to 1 - * @param {number} maxHue Maximum hue - int - * @param {number} minHue Minimum hue - int - * @returns {string} Color in hex + * @param percentage float, 0 to 1 + * @param maxHue Maximum hue - int + * @param minHue Minimum hue - int + * @returns Color in hex */ -exports.percentageToColor = (percentage, maxHue = 90, minHue = 10) => { +export function percentageToColor(percentage: number, maxHue: number = 90, minHue: number = 10): string { const hue = percentage * (maxHue - minHue) + minHue; try { return chroma(`hsl(${hue}, 90%, 40%)`).hex(); } catch (err) { return badgeConstants.naColor; } -}; +} /** * Joins and array of string to one string after filtering out empty values - * @param {string[]} parts Strings to join - * @param {string} connector Separator for joined strings - * @returns {string} Joined strings + * @param parts Strings to join + * @param connector Separator for joined strings + * @returns Joined strings */ -exports.filterAndJoin = (parts, connector = "") => { +export function filterAndJoin(parts: string[], connector: string = ""): string { return parts.filter((part) => !!part && part !== "").join(connector); -}; +} /** * Send an Error response - * @param {object} res Express response object - * @param {string} msg Message to send - * @returns {void} + * @param res Express response object + * @param msg Message to send + * @returns */ -module.exports.sendHttpError = (res, msg = "") => { +export function sendHttpError(res: object, msg: string = ""): void { if (msg.includes("SQLITE_BUSY") || msg.includes("SQLITE_LOCKED")) { res.status(503).json({ status: "fail", @@ -592,16 +604,16 @@ module.exports.sendHttpError = (res, msg = "") => { msg: msg, }); } -}; +} /** * Convert timezone of time object - * @param {object} obj Time object to update - * @param {string} timezone New timezone to set - * @param {boolean} timeObjectToUTC Convert time object to UTC - * @returns {object} Time object with updated timezone + * @param obj Time object to update + * @param timezone New timezone to set + * @param timeObjectToUTC Convert time object to UTC + * @returns Time object with updated timezone */ -function timeObjectConvertTimezone(obj, timezone, timeObjectToUTC = true) { +function timeObjectConvertTimezone(obj: object, timezone: string, timeObjectToUTC: boolean = true): object { let offsetString; if (timezone) { @@ -641,29 +653,29 @@ function timeObjectConvertTimezone(obj, timezone, timeObjectToUTC = true) { /** * Convert time object to UTC - * @param {object} obj Object to convert - * @param {string} timezone Timezone of time object - * @returns {object} Updated time object + * @param obj Object to convert + * @param timezone Timezone of time object + * @returns Updated time object */ -module.exports.timeObjectToUTC = (obj, timezone = undefined) => { +export function timeObjectToUTC(obj: object, timezone: string = undefined): object { return timeObjectConvertTimezone(obj, timezone, true); -}; +} /** * Convert time object to local time - * @param {object} obj Object to convert - * @param {string} timezone Timezone to convert to - * @returns {object} Updated object + * @param obj Object to convert + * @param timezone Timezone to convert to + * @returns Updated object */ -module.exports.timeObjectToLocal = (obj, timezone = undefined) => { +export function timeObjectToLocal(obj: object, timezone: string = undefined): object { return timeObjectConvertTimezone(obj, timezone, false); -}; +} /** * Returns an array of SHA256 fingerprints for all known root certificates. - * @returns {Set} A set of SHA256 fingerprints. + * @returns A set of SHA256 fingerprints. */ -module.exports.rootCertificatesFingerprints = () => { +export function rootCertificatesFingerprints(): Set { let fingerprints = tls.rootCertificates.map((cert) => { let certLines = cert.split("\n"); certLines.shift(); @@ -688,48 +700,44 @@ module.exports.rootCertificatesFingerprints = () => { ); // ISRG X2 cross-signed with ISRG X1 return new Set(fingerprints); -}; +} -module.exports.SHAKE256_LENGTH = 16; +export const SHAKE256_LENGTH = 16; /** - * @param {string} data The data to be hashed - * @param {number} len Output length of the hash - * @returns {string} The hashed data in hex format + * @param data The data to be hashed + * @param len Output length of the hash + * @returns The hashed data in hex format */ -module.exports.shake256 = (data, len) => { +export function shake256(data: string, len: number): string { if (!data) { return ""; } return crypto.createHash("shake256", { outputLength: len }).update(data).digest("hex"); -}; +} /** * Non await sleep * Source: https://stackoverflow.com/questions/59099454/is-there-a-way-to-call-sleep-without-await-keyword - * @param {number} n Milliseconds to wait - * @returns {void} + * @param n Milliseconds to wait + * @returns */ -module.exports.wait = (n) => { +export function wait(n: number): void { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, n); -}; +} // For unit test, export functions -if (process.env.TEST_BACKEND) { - module.exports.__test = { - parseCertificateInfo, - }; - module.exports.__getPrivateFunction = (functionName) => { - return module.exports.__test[functionName]; - }; +export const __test = process.env.TEST_BACKEND ? { parseCertificateInfo } : undefined; +export function __getPrivateFunction(functionName) { + return __test?.[functionName]; } /** * Generates an abort signal with the specified timeout. - * @param {number} timeoutMs - The timeout in milliseconds. - * @returns {AbortSignal | null} - The generated abort signal, or null if not supported. + * @param timeoutMs - The timeout in milliseconds. + * @returns - The generated abort signal, or null if not supported. */ -module.exports.axiosAbortSignal = (timeoutMs) => { +export function axiosAbortSignal(timeoutMs: number): AbortSignal | null { try { // Just in case, as 0 timeout here will cause the request to be aborted immediately if (!timeoutMs || timeoutMs <= 0) { @@ -748,41 +756,39 @@ module.exports.axiosAbortSignal = (timeoutMs) => { return null; } } -}; +} /** * Async version of fs.existsSync - * @param {PathLike} path File path - * @returns {Promise} True if file exists, false otherwise + * @param path File path + * @returns True if file exists, false otherwise */ -function fsExists(path) { +export function fsExists(path: string | Buffer): Promise { return new Promise(function (resolve, reject) { exists(path, function (exists) { resolve(exists); }); }); } -module.exports.fsExists = fsExists; /** * Encode user and password to Base64 encoding * for HTTP "basic" auth, as per RFC-7617 - * @param {string|null} user - The username (defaults to empty string if null/undefined) - * @param {string|null} pass - The password (defaults to empty string if null/undefined) - * @returns {string} Encoded Base64 string + * @param user - The username (defaults to empty string if null/undefined) + * @param pass - The password (defaults to empty string if null/undefined) + * @returns Encoded Base64 string */ -function encodeBase64(user, pass) { +export function encodeBase64(user: string | null, pass: string | null): string { return Buffer.from(`${user || ""}:${pass || ""}`).toString("base64"); } -module.exports.encodeBase64 = encodeBase64; /** * checks certificate chain for expiring certificates - * @param {object} monitor - The monitor object - * @param {object} tlsInfoObject Information about certificate - * @returns {Promise} + * @param monitor - The monitor object + * @param tlsInfoObject Information about certificate + * @returns */ -async function checkCertExpiryNotifications(monitor, tlsInfoObject) { +export async function checkCertExpiryNotifications(monitor: object, tlsInfoObject: object): Promise { if (!tlsInfoObject || !tlsInfoObject.certInfo || !tlsInfoObject.certInfo.daysRemaining) { return; } @@ -837,15 +843,14 @@ async function checkCertExpiryNotifications(monitor, tlsInfoObject) { } } } -module.exports.checkCertExpiryNotifications = checkCertExpiryNotifications; /** * By default, command-exists will throw a null error if the command does not exist, which is ugly. The function makes it better. * Read more: https://github.com/mathisonian/command-exists/issues/22 - * @param {string} command Command to check - * @returns {Promise} True if command exists, false otherwise + * @param command Command to check + * @returns True if command exists, false otherwise */ -async function commandExists(command) { +export async function commandExists(command: string): Promise { try { await require("command-exists")(command); return true; @@ -853,19 +858,18 @@ async function commandExists(command) { return false; } } -module.exports.commandExists = commandExists; /** * Log the server's listening URLs, similar to Vite's dev server output. * When no hostname is specified (bound to all interfaces), it prints * localhost plus every non-internal network address. - * @param {string} tag Log tag (e.g. "server", "setup-database") - * @param {number} port Port number - * @param {string} hostname Bound hostname, if any - * @param {boolean} isHTTPS Whether the server is using HTTPS - * @returns {void} + * @param tag Log tag (e.g. "server", "setup-database") + * @param port Port number + * @param hostname Bound hostname, if any + * @param isHTTPS Whether the server is using HTTPS + * @returns */ -module.exports.printServerUrls = (tag, port, hostname, isHTTPS = false) => { +export function printServerUrls(tag: string, port: number, hostname: string, isHTTPS: boolean = false): void { try { // If hostname is specified, just print that one. if (hostname) { @@ -909,19 +913,58 @@ module.exports.printServerUrls = (tag, port, hostname, isHTTPS = false) => { } catch (e) { log.error(tag, "Error printing server URLs: " + e.message); } -}; +} /** * Construct a URL a bit more safely - * @param {boolean} isHTTPS Whether the URL should use HTTPS protocol - * @param {string} hostname The hostname to use in the URL - * @param {number} port The port - * @returns {string} The constructed URL as a string + * @param isHTTPS Whether the URL should use HTTPS protocol + * @param hostname The hostname to use in the URL + * @param port The port + * @returns The constructed URL as a string */ -function createURL(isHTTPS, hostname, port = 80) { +function createURL(isHTTPS: boolean, hostname: string, port: number = 80): string { const url = new URL((isHTTPS ? "https" : "http") + `://` + hostname); url.port = String(port); // Prefer origin if available, it doesn't contain the trailing slash return url.origin || url.toString(); } + +/** + * Extend dayjs with the plugins the backend uses + * @returns The shared dayjs instance + */ +export function initDayjs(): object { + dayjs.extend(utc); + dayjs.extend(timezone); + dayjs.extend(customParseFormat); + return dayjs; +} + +/** + * @param res Express response + * @param e Any error + * @returns + */ +export function generalErrorResponse(res: Response, e: unknown): void { + if (e instanceof ZodError) { + let message = ""; + for (const issue of e.issues) { + message += `${issue.path.join(".")}: ${issue.message}\n`; + } + res.status(400).json({ + ok: false, + msg: message, + }); + } else if (e instanceof Error) { + res.status(400).json({ + ok: false, + msg: e.message, + }); + } else { + res.status(400).json({ + ok: false, + msg: "Unknown error", + }); + } +} diff --git a/server/util2.ts b/server/util2.ts deleted file mode 100644 index 8d06d86f1..000000000 --- a/server/util2.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ZodError } from "zod"; -import { Response } from "express"; - -/** - * @param res Express response - * @param e Any error - */ -export function generalErrorResponse(res: Response, e: unknown) { - if (e instanceof ZodError) { - let message = ""; - for (const issue of e.issues) { - message += `${issue.path.join(".")}: ${issue.message}\n`; - } - res.status(400).json({ - ok: false, - msg: message, - }); - } else if (e instanceof Error) { - res.status(400).json({ - ok: false, - msg: e.message, - }); - } else { - res.status(400).json({ - ok: false, - msg: "Unknown error", - }); - } -} diff --git a/server/utils/simple-migration-server.js b/server/utils/simple-migration-server.js index 524584035..c40eb3d30 100644 --- a/server/utils/simple-migration-server.js +++ b/server/utils/simple-migration-server.js @@ -1,6 +1,6 @@ const express = require("express"); const http = require("node:http"); -const { printServerUrls } = require("../util-server"); +const { printServerUrls } = require("../util"); /** * SimpleMigrationServer diff --git a/src/main.js b/src/main.js index 4c8858367..89eea7aad 100644 --- a/src/main.js +++ b/src/main.js @@ -16,14 +16,9 @@ import theme from "./mixins/theme"; import lang from "./mixins/lang"; import { router } from "./router"; import { appName } from "./util.ts"; -import dayjs from "dayjs"; -import timezone from "./modules/dayjs/plugin/timezone"; -import utc from "dayjs/plugin/utc"; -import relativeTime from "dayjs/plugin/relativeTime"; -import { loadToastSettings } from "./util-frontend"; -dayjs.extend(utc); -dayjs.extend(timezone); -dayjs.extend(relativeTime); +import { initDayjs, loadToastSettings } from "./util-frontend"; + +initDayjs(); const app = createApp({ mixins: [socket, theme, mobile, datetime, publicMixin, lang], diff --git a/src/mixins/socket.js b/src/mixins/socket.js index f215e05d1..98c8feba8 100644 --- a/src/mixins/socket.js +++ b/src/mixins/socket.js @@ -6,7 +6,7 @@ import dayjs from "dayjs"; import mitt from "mitt"; import { DOWN, MAINTENANCE, PENDING, UP } from "../util.ts"; -import { getToastSuccessTimeout, getToastErrorTimeout } from "../util-frontend.js"; +import { getToastSuccessTimeout, getToastErrorTimeout } from "../util-frontend"; import { BeatVersionThrottle } from "../beat-version-throttle"; import { applyBeat, applyLatestBeats, beatStatusEntry } from "../beat-state"; import { logout as betterAuthLogout } from "../auth-client"; diff --git a/src/modules/dayjs/constant.js b/src/modules/dayjs/constant.js deleted file mode 100644 index 741a623b7..000000000 --- a/src/modules/dayjs/constant.js +++ /dev/null @@ -1,25 +0,0 @@ -export let SECONDS_A_MINUTE = 60; -export let SECONDS_A_HOUR = SECONDS_A_MINUTE * 60; -export let SECONDS_A_DAY = SECONDS_A_HOUR * 24; -export let SECONDS_A_WEEK = SECONDS_A_DAY * 7; -export let MILLISECONDS_A_SECOND = 1e3; -export let MILLISECONDS_A_MINUTE = SECONDS_A_MINUTE * MILLISECONDS_A_SECOND; -export let MILLISECONDS_A_HOUR = SECONDS_A_HOUR * MILLISECONDS_A_SECOND; -export let MILLISECONDS_A_DAY = SECONDS_A_DAY * MILLISECONDS_A_SECOND; -export let MILLISECONDS_A_WEEK = SECONDS_A_WEEK * MILLISECONDS_A_SECOND; // English locales - -export let MS = "millisecond"; -export let S = "second"; -export let MIN = "minute"; -export let H = "hour"; -export let D = "day"; -export let W = "week"; -export let M = "month"; -export let Q = "quarter"; -export let Y = "year"; -export let DATE = "date"; -export let FORMAT_DEFAULT = "YYYY-MM-DDTHH:mm:ssZ"; -export let INVALID_DATE_STRING = "Invalid Date"; // regex - -export let REGEX_PARSE = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/; -export let REGEX_FORMAT = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g; diff --git a/src/modules/dayjs/plugin/timezone/index.d.ts b/src/modules/dayjs/plugin/timezone/index.d.ts deleted file mode 100644 index ce8e98e1d..000000000 --- a/src/modules/dayjs/plugin/timezone/index.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { PluginFunc, ConfigType } from "dayjs/esm"; - -declare const plugin: PluginFunc; -export = plugin; - -declare module "dayjs/esm" { - interface Dayjs { - tz(timezone?: string, keepLocalTime?: boolean): Dayjs; - offsetName(type?: "short" | "long"): string | undefined; - } - - interface DayjsTimezone { - (date: ConfigType, timezone?: string): Dayjs; - (date: ConfigType, format: string, timezone?: string): Dayjs; - guess(): string; - setDefault(timezone?: string): void; - } - - const tz: DayjsTimezone; -} diff --git a/src/modules/dayjs/plugin/timezone/index.js b/src/modules/dayjs/plugin/timezone/index.js deleted file mode 100644 index c377c006e..000000000 --- a/src/modules/dayjs/plugin/timezone/index.js +++ /dev/null @@ -1,204 +0,0 @@ -/** - * Copy from node_modules/dayjs/plugin/timezone.js - * Try to fix https://github.com/louislam/uptime-kuma/issues/2318 - * Source: https://github.com/iamkun/dayjs/tree/dev/src/plugin/utc - * License: MIT - */ -import { MIN, MS } from "../../constant"; -let typeToPos = { - year: 0, - month: 1, - day: 2, - hour: 3, - minute: 4, - second: 5, -}; // Cache time-zone lookups from Intl.DateTimeFormat, -// as it is a *very* slow method. - -let dtfCache = {}; - -let getDateTimeFormat = function getDateTimeFormat(timezone, options) { - if (options === void 0) { - options = {}; - } - - let timeZoneName = options.timeZoneName || "short"; - let key = timezone + "|" + timeZoneName; - let dtf = dtfCache[key]; - - if (!dtf) { - dtf = new Intl.DateTimeFormat("en-US", { - hour12: false, - timeZone: timezone, - year: "numeric", - month: "2-digit", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - timeZoneName: timeZoneName, - }); - dtfCache[key] = dtf; - } - - return dtf; -}; - -export default (function (o, c, d) { - let defaultTimezone; - - let makeFormatParts = function makeFormatParts(timestamp, timezone, options) { - if (options === void 0) { - options = {}; - } - - let date = new Date(timestamp); - let dtf = getDateTimeFormat(timezone, options); - return dtf.formatToParts(date); - }; - - let tzOffset = function tzOffset(timestamp, timezone) { - let formatResult = makeFormatParts(timestamp, timezone); - let filled = []; - - for (let i = 0; i < formatResult.length; i += 1) { - let _formatResult$i = formatResult[i]; - let type = _formatResult$i.type; - let value = _formatResult$i.value; - let pos = typeToPos[type]; - - if (pos >= 0) { - filled[pos] = parseInt(value, 10); - } - } - - let hour = filled[3]; // Workaround for the same behavior in different node version - // https://github.com/nodejs/node/issues/33027 - - /* istanbul ignore next */ - - let fixedHour = hour === 24 ? 0 : hour; - let utcString = - filled[0] + - "-" + - filled[1] + - "-" + - filled[2] + - " " + - fixedHour + - ":" + - filled[4] + - ":" + - filled[5] + - ":000"; - let utcTs = d.utc(utcString).valueOf(); - let asTS = +timestamp; - let over = asTS % 1000; - asTS -= over; - return (utcTs - asTS) / (60 * 1000); - }; // find the right offset a given local time. The o input is our guess, which determines which - // offset we'll pick in ambiguous cases (e.g. there are two 3 AMs b/c Fallback DST) - // https://github.com/moment/luxon/blob/master/src/datetime.js#L76 - - let fixOffset = function fixOffset(localTS, o0, tz) { - // Our UTC time is just a guess because our offset is just a guess - let utcGuess = localTS - o0 * 60 * 1000; // Test whether the zone matches the offset for this ts - - let o2 = tzOffset(utcGuess, tz); // If so, offset didn't change and we're done - - if (o0 === o2) { - return [utcGuess, o0]; - } // If not, change the ts by the difference in the offset - - utcGuess -= (o2 - o0) * 60 * 1000; // If that gives us the local time we want, we're done - - let o3 = tzOffset(utcGuess, tz); - - if (o2 === o3) { - return [utcGuess, o2]; - } // If it's different, we're in a hole time. - // The offset has changed, but the we don't adjust the time - - return [localTS - Math.min(o2, o3) * 60 * 1000, Math.max(o2, o3)]; - }; - - let proto = c.prototype; - - proto.tz = function (timezone, keepLocalTime) { - if (timezone === void 0) { - timezone = defaultTimezone; - } - - let oldOffset = this.utcOffset(); - let date = this.toDate(); - let target = date - .toLocaleString("en-US", { - timeZone: timezone, - }) - .replace("\u202f", " "); - let diff = Math.round((date - new Date(target)) / 1000 / 60); - let ins = d(target) - .$set(MS, this.$ms) - .utcOffset(-Math.round(date.getTimezoneOffset() / 15) * 15 - diff, true); - - if (keepLocalTime) { - let newOffset = ins.utcOffset(); - ins = ins.add(oldOffset - newOffset, MIN); - } - - ins.$x.$timezone = timezone; - return ins; - }; - - proto.offsetName = function (type) { - // type: short(default) / long - let zone = this.$x.$timezone || d.tz.guess(); - let result = makeFormatParts(this.valueOf(), zone, { - timeZoneName: type, - }).find(function (m) { - return m.type.toLowerCase() === "timezonename"; - }); - return result && result.value; - }; - - let oldStartOf = proto.startOf; - - proto.startOf = function (units, startOf) { - if (!this.$x || !this.$x.$timezone) { - return oldStartOf.call(this, units, startOf); - } - - let withoutTz = d(this.format("YYYY-MM-DD HH:mm:ss:SSS")); - let startOfWithoutTz = oldStartOf.call(withoutTz, units, startOf); - return startOfWithoutTz.tz(this.$x.$timezone, true); - }; - - d.tz = function (input, arg1, arg2) { - let parseFormat = arg2 && arg1; - let timezone = arg2 || arg1 || defaultTimezone; - let previousOffset = tzOffset(+d(), timezone); - - if (typeof input !== "string") { - // timestamp number || js Date || Day.js - return d(input).tz(timezone); - } - - let localTs = d.utc(input, parseFormat).valueOf(); - - let _fixOffset = fixOffset(localTs, previousOffset, timezone); - let targetTs = _fixOffset[0]; - let targetOffset = _fixOffset[1]; - - let ins = d(targetTs).utcOffset(targetOffset); - ins.$x.$timezone = timezone; - return ins; - }; - - d.tz.guess = function () { - return Intl.DateTimeFormat().resolvedOptions().timeZone; - }; - - d.tz.setDefault = function (timezone) { - defaultTimezone = timezone; - }; -}); diff --git a/src/util-frontend.js b/src/util-frontend.ts similarity index 59% rename from src/util-frontend.js rename to src/util-frontend.ts index 2ea45af27..ff8d3db6d 100644 --- a/src/util-frontend.js +++ b/src/util-frontend.ts @@ -1,17 +1,43 @@ import dayjs from "dayjs"; +import type { Dayjs } from "dayjs"; +import relativeTime from "dayjs/plugin/relativeTime"; +import timezone from "dayjs/plugin/timezone"; +import utc from "dayjs/plugin/utc"; import { getTimeZones } from "@vvo/tzdb"; import { localeDirection, currentLocale } from "./i18n"; import { POSITION } from "vue-toastification"; import { baseURL } from "./auth-client"; +interface TimezoneOption { + name: string; + value: string; + time: number; +} + +interface ToastPluginOptions { + position: string; + containerClassName: string; + showCloseButtonOnHover: boolean; + filterBeforeCreate: (toast: { timeout?: number }, toasts: unknown[]) => unknown; +} + +/** + * Extend dayjs with the plugins the frontend uses + * @returns The shared dayjs instance + */ +export function initDayjs(): Dayjs { + dayjs.extend(utc); + dayjs.extend(timezone); + dayjs.extend(relativeTime); + return dayjs; +} + /** * Returns the offset from UTC in hours for the current locale. - * @param {string} timeZone Timezone to get offset for - * @returns {number} The offset from UTC in hours. - * - * Generated by Trelent + * @param timeZone Timezone to get offset for + * @returns The offset from UTC in hours. */ -function getTimezoneOffset(timeZone) { +function getTimezoneOffset(timeZone: string): number { const now = new Date(); const tzString = now.toLocaleString("en-US", { timeZone, @@ -24,24 +50,22 @@ function getTimezoneOffset(timeZone) { /** * Returns a list of timezones sorted by their offset from UTC. - * @returns {object[]} A list of the given timezones sorted by their offset from UTC. - * - * Generated by Trelent + * @returns A list of the given timezones sorted by their offset from UTC. */ -export function timezoneList() { - let result = []; +export function timezoneList(): TimezoneOption[] { + const result: TimezoneOption[] = []; const timeZones = getTimeZones(); - for (let timezone of timeZones) { + for (const timezone of timeZones) { try { - let display = dayjs().tz(timezone.name).format("Z"); + const display = dayjs().tz(timezone.name).format("Z"); result.push({ name: `(UTC${display}) ${timezone.name}`, value: timezone.name, time: getTimezoneOffset(timezone.name), }); - } catch (e) { + } catch { // Skipping not supported timezone.name by dayjs } } @@ -63,9 +87,9 @@ export function timezoneList() { /** * Set the locale of the HTML page - * @returns {void} + * @returns No return value. */ -export function setPageLocale() { +export function setPageLocale(): void { const html = document.documentElement; html.setAttribute("lang", currentLocale()); html.setAttribute("dir", localeDirection()); @@ -74,19 +98,19 @@ export function setPageLocale() { /** * Get the base URL * Mainly used for dev, because the backend and the frontend are in different ports. - * @returns {string} Base URL + * @returns Base URL */ -export function getResBaseURL() { +export function getResBaseURL(): string { return baseURL; } /** * Get the tag color options * Shared between components - * @param {any} self Component - * @returns {object[]} Colour options + * @param self Component + * @returns Colour options */ -export function colorOptions(self) { +export function colorOptions(self: any): { name: string; color: string }[] { return [ { name: self.$t("Gray"), color: "#4B5563" }, { name: self.$t("Red"), color: "#DC2626" }, @@ -101,9 +125,9 @@ export function colorOptions(self) { /** * Loads the toast timeout settings from storage. - * @returns {object} The toast plugin options object. + * @returns The toast plugin options object. */ -export function loadToastSettings() { +export function loadToastSettings(): ToastPluginOptions { return { position: POSITION.BOTTOM_RIGHT, containerClassName: "toast-container mb-5", @@ -121,13 +145,14 @@ export function loadToastSettings() { /** * Get timeout for success toasts - * @returns {(number|boolean)} Timeout in ms. If false timeout disabled. + * @returns Timeout in ms. If false timeout disabled. */ -export function getToastSuccessTimeout() { - let successTimeout = 20000; +export function getToastSuccessTimeout(): number | false { + let successTimeout: number | false = 20000; + const storedTimeout = (localStorage as any).toastSuccessTimeout; - if (localStorage.toastSuccessTimeout !== undefined) { - const parsedTimeout = parseInt(localStorage.toastSuccessTimeout); + if (storedTimeout !== undefined) { + const parsedTimeout = parseInt(storedTimeout); if (parsedTimeout != null && !Number.isNaN(parsedTimeout)) { successTimeout = parsedTimeout; } @@ -142,13 +167,14 @@ export function getToastSuccessTimeout() { /** * Get timeout for error toasts - * @returns {(number|boolean)} Timeout in ms. If false timeout disabled. + * @returns Timeout in ms. If false timeout disabled. */ -export function getToastErrorTimeout() { - let errorTimeout = -1; +export function getToastErrorTimeout(): number | false { + let errorTimeout: number | false = -1; + const storedTimeout = (localStorage as any).toastErrorTimeout; - if (localStorage.toastErrorTimeout !== undefined) { - const parsedTimeout = parseInt(localStorage.toastErrorTimeout); + if (storedTimeout !== undefined) { + const parsedTimeout = parseInt(storedTimeout); if (parsedTimeout != null && !Number.isNaN(parsedTimeout)) { errorTimeout = parsedTimeout; } @@ -162,14 +188,22 @@ export function getToastErrorTimeout() { } class TimeDurationFormatter { + private durationFormatOptions: { style: string }; + private relativeTimeFormatOptions: { numeric: string }; + private durationFormatInstance?: { format: (duration: object) => string }; + private relativeTimeFormatInstance?: Intl.RelativeTimeFormat; + /** * Default locale and options for Time Duration Formatter (supports both DurationFormat and RelativeTimeFormat) */ constructor() { this.durationFormatOptions = { style: "long" }; this.relativeTimeFormatOptions = { numeric: "always" }; - if (Intl.DurationFormat !== undefined) { - this.durationFormatInstance = new Intl.DurationFormat(currentLocale(), this.durationFormatOptions); + + const DurationFormat = (Intl as any).DurationFormat; + + if (DurationFormat !== undefined) { + this.durationFormatInstance = new DurationFormat(currentLocale(), this.durationFormatOptions); } else { this.relativeTimeFormatInstance = new Intl.RelativeTimeFormat( currentLocale(), @@ -180,12 +214,14 @@ class TimeDurationFormatter { /** * Method to update the instance locale and options - * @param {string} locale Localization identifier (e.g., "en", "ar-sy") to update the instance with. - * @returns {void} No return value. + * @param locale Localization identifier (e.g., "en", "ar-sy") to update the instance with. + * @returns No return value. */ - updateLocale(locale) { - if (Intl.DurationFormat !== undefined) { - this.durationFormatInstance = new Intl.DurationFormat(locale, this.durationFormatOptions); + updateLocale(locale: string): void { + const DurationFormat = (Intl as any).DurationFormat; + + if (DurationFormat !== undefined) { + this.durationFormatInstance = new DurationFormat(locale, this.durationFormatOptions); } else { this.relativeTimeFormatInstance = new Intl.RelativeTimeFormat(locale, this.relativeTimeFormatOptions); } @@ -193,10 +229,10 @@ class TimeDurationFormatter { /** * Method to convert seconds into Human readable format - * @param {number} seconds Receive value in seconds. - * @returns {string} String converted to Days Mins Seconds Format + * @param seconds Receive value in seconds. + * @returns String converted to Days Mins Seconds Format */ - secondsToHumanReadableFormat(seconds) { + secondsToHumanReadableFormat(seconds: number): string { const days = Math.floor(seconds / 86400); const hours = Math.floor((seconds % 86400) / 3600); const minutes = Math.floor(((seconds % 86400) % 3600) / 60); @@ -212,18 +248,18 @@ class TimeDurationFormatter { }); } - const parts = []; + const parts: string[] = []; /** * Build the formatted string from parts * 1. Get the relative time formatted parts from the instance. * 2. Filter out the relevant parts literal (unit of time) or integer (value). * 3. Map out the required values. - * @param {number} value Receives value in seconds. - * @param {string} unitOfTime Expected unit of time after conversion. - * @returns {void} + * @param value Receives value in seconds. + * @param unitOfTime Expected unit of time after conversion. + * @returns No return value. */ - const toFormattedPart = (value, unitOfTime) => { - const partsArray = this.relativeTimeFormatInstance.formatToParts(value, unitOfTime); + const toFormattedPart = (value: number, unitOfTime: Intl.RelativeTimeFormatUnit) => { + const partsArray = this.relativeTimeFormatInstance!.formatToParts(value, unitOfTime); const filteredParts = partsArray .filter((part, index) => part.type === "integer" || (part.type === "literal" && index > 0)) .map((part) => part.value); @@ -248,7 +284,7 @@ class TimeDurationFormatter { if (parts.length > 0) { return `${parts.join(" ")}`; } - return this.relativeTimeFormatInstance.format(0, "second"); // Handle case for 0 seconds + return this.relativeTimeFormatInstance!.format(0, "second"); // Handle case for 0 seconds } } diff --git a/src/util.ts b/src/util.ts index a1f66884e..fb71f9188 100644 --- a/src/util.ts +++ b/src/util.ts @@ -3,14 +3,7 @@ // Common Util for frontend and backend */ -import dayjsFrontend from "dayjs"; - -// For dayjs plugins' type checking, don't remove event though it is not used in this file -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import * as timezone from "dayjs/plugin/timezone"; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import * as utc from "dayjs/plugin/utc"; - +import dayjs from "dayjs"; import jsonata from "jsonata"; /** @@ -23,12 +16,6 @@ export function isDevEnv() { export const isNode = typeof process !== "undefined" && process?.versions?.node; -/** - * Smarter dayjs import that supports both frontend and backend - * @returns {dayjs.Dayjs} dayjs instance - */ -const dayjs = isNode ? require("dayjs") : dayjsFrontend; - export const devOriginList = [ "http://127.0.0.1:3000", "http://127.0.0.1:3001", diff --git a/test/backend-test/monitors/test-kafka-producer.js b/test/backend-test/monitors/test-kafka-producer.js index ec8dd5022..c6e7a63f5 100644 --- a/test/backend-test/monitors/test-kafka-producer.js +++ b/test/backend-test/monitors/test-kafka-producer.js @@ -1,6 +1,6 @@ const { describe, test } = require("node:test"); const assert = require("node:assert"); -const { kafkaProducerAsync } = require("../../../server/util-server"); +const { kafkaProducerAsync } = require("../../../server/util"); describe("Kafka Producer", () => { test("rejects when broker is not reachable", async () => { diff --git a/test/backend-test/test-cert-hostname-match.js b/test/backend-test/test-cert-hostname-match.js index f35b7aa76..b430ba5d4 100644 --- a/test/backend-test/test-cert-hostname-match.js +++ b/test/backend-test/test-cert-hostname-match.js @@ -2,7 +2,7 @@ const { describe, test } = require("node:test"); const assert = require("node:assert"); -const { checkCertificateHostname } = require("../../server/util-server"); +const { checkCertificateHostname } = require("../../server/util"); const testCert = ` -----BEGIN CERTIFICATE----- diff --git a/test/backend-test/test-domain.js b/test/backend-test/test-domain.js index d48f5ee48..678f8d593 100644 --- a/test/backend-test/test-domain.js +++ b/test/backend-test/test-domain.js @@ -8,10 +8,9 @@ const TestDB = require("../mock-testdb"); const { R } = require("redbean-node"); const { Notification } = require("../../server/notification"); const { Settings } = require("../../server/settings"); -const { setSetting } = require("../../server/util-server"); +const { setSetting } = require("../../server/util"); const dayjs = require("dayjs"); const { retryExternalService } = require("./test-util"); -dayjs.extend(require("dayjs/plugin/utc")); const testDb = new TestDB("./data/test-domain"); diff --git a/test/backend-test/test-globalping.js b/test/backend-test/test-globalping.js index a02cbbceb..d7ea21643 100644 --- a/test/backend-test/test-globalping.js +++ b/test/backend-test/test-globalping.js @@ -1,6 +1,6 @@ const { describe, test, mock } = require("node:test"); const assert = require("node:assert"); -const { encodeBase64 } = require("../../server/util-server"); +const { encodeBase64 } = require("../../server/util"); const { UP, PENDING } = require("../../src/util"); describe("GlobalpingMonitorType", () => { diff --git a/test/backend-test/test-maintenance.ts b/test/backend-test/test-maintenance.ts index 2812a87a9..75a646541 100644 --- a/test/backend-test/test-maintenance.ts +++ b/test/backend-test/test-maintenance.ts @@ -4,9 +4,6 @@ import { createRequire } from "node:module"; import dayjs from "dayjs"; const require = createRequire(import.meta.url); -dayjs.extend(require("dayjs/plugin/utc")); -dayjs.extend(require("dayjs/plugin/customParseFormat")); -dayjs.extend(require("../../server/modules/dayjs/plugin/timezone")); const Maintenance = require("../../server/model/maintenance"); diff --git a/test/backend-test/test-radius.js b/test/backend-test/test-radius.js index 9d8eb8531..cccf55368 100644 --- a/test/backend-test/test-radius.js +++ b/test/backend-test/test-radius.js @@ -2,7 +2,7 @@ const { describe, test } = require("node:test"); const assert = require("node:assert/strict"); const dgram = require("dgram"); const radius = require("radius"); -const { radius: radiusCheck } = require("../../server/util-server"); +const { radius: radiusCheck } = require("../../server/util"); /** * Start a UDP RADIUS server that replies to every Access-Request with the given response code. diff --git a/test/backend-test/test-status-page.js b/test/backend-test/test-status-page.js index 3a3060562..2e2834d7f 100644 --- a/test/backend-test/test-status-page.js +++ b/test/backend-test/test-status-page.js @@ -1,8 +1,6 @@ const { describe, test, mock } = require("node:test"); const assert = require("node:assert"); const StatusPage = require("../../server/model/status_page"); -const dayjs = require("dayjs"); -const utc = require("dayjs/plugin/utc"); const { STATUS_PAGE_ALL_UP, STATUS_PAGE_ALL_DOWN, @@ -10,8 +8,6 @@ const { STATUS_PAGE_MAINTENANCE, } = require("../../src/util"); -dayjs.extend(utc); - describe("StatusPage", () => { describe("getStatusDescription()", () => { test("returns 'No Services' when status is -1", () => { diff --git a/test/backend-test/test-uptime-calculator-storage.ts b/test/backend-test/test-uptime-calculator-storage.ts index ae14a56e5..71c70e60f 100644 --- a/test/backend-test/test-uptime-calculator-storage.ts +++ b/test/backend-test/test-uptime-calculator-storage.ts @@ -7,7 +7,6 @@ const { UptimeCalculator } = require("../../server/uptime-calculator"); const { R } = require("redbean-node"); const dayjs = require("dayjs"); const { UP } = require("../../src/util"); -dayjs.extend(require("dayjs/plugin/utc")); const originals = { getAll: R.getAll, diff --git a/test/backend-test/test-uptime-calculator.js b/test/backend-test/test-uptime-calculator.js index d3508f159..6f48c39a1 100644 --- a/test/backend-test/test-uptime-calculator.js +++ b/test/backend-test/test-uptime-calculator.js @@ -2,10 +2,8 @@ const { describe, test } = require("node:test"); const assert = require("node:assert"); const { UptimeCalculator } = require("../../server/uptime-calculator"); const dayjs = require("dayjs"); + const { UP, DOWN, PENDING, MAINTENANCE } = require("../../src/util"); -dayjs.extend(require("dayjs/plugin/utc")); -dayjs.extend(require("../../server/modules/dayjs/plugin/timezone")); -dayjs.extend(require("dayjs/plugin/customParseFormat")); /** * Create a calculator whose bucket loading is stubbed, so update() does not query the database diff --git a/test/backend-test/test-util.js b/test/backend-test/test-util.js index 7e0b17c60..28b54b39c 100644 --- a/test/backend-test/test-util.js +++ b/test/backend-test/test-util.js @@ -33,9 +33,6 @@ async function retryExternalService(testFn, maxAttempts = 5) { module.exports = { retryExternalService }; -dayjs.extend(require("dayjs/plugin/utc")); -dayjs.extend(require("dayjs/plugin/customParseFormat")); - describe("Server Utilities", () => { test("SQL_DATETIME_FORMAT constant matches MariaDB/MySQL format", () => { assert.strictEqual(SQL_DATETIME_FORMAT, "YYYY-MM-DD HH:mm:ss"); diff --git a/test/setup.ts b/test/setup.ts new file mode 100644 index 000000000..4426119df --- /dev/null +++ b/test/setup.ts @@ -0,0 +1,3 @@ +import { initDayjs } from "../server/util"; + +initDayjs();