diff --git a/.eslintrc.js b/.eslintrc.js index 4feb349e..19f4e403 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -7,16 +7,8 @@ module.exports = { plugins: ['node', '@typescript-eslint'], - // We don't use babel for transpiling, but we do use features - // supported by Node.js that the default eslint parser does not - // yet understand. - parser: '@babel/eslint-parser', parserOptions: { sourceType: 'script', - requireConfigFile: false, - babelOptions: { - plugins: ['@babel/plugin-syntax-class-properties'], - }, }, rules: { @@ -68,6 +60,9 @@ module.exports = { }, { files: ['*.mjs'], + parserOptions: { + sourceType: 'module', + }, rules: { 'import/extensions': ['error', 'ignorePackages'], 'import/no-unresolved': ['error', { diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbd9e630..226f8a15 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,12 +35,12 @@ jobs: name: Tests strategy: matrix: - node-version: ['12.x', '14.x', '16.x', '18.x'] + node-version: ['14.x', '16.x', '18.x'] mongo-version: ['5.0'] include: - - node-version: '12.x' + - node-version: 'lts/*' mongo-version: '4.2' - - node-version: '12.x' + - node-version: 'lts/*' mongo-version: '4.4' runs-on: ubuntu-latest services: diff --git a/dev/u-wave-dev-server b/dev/u-wave-dev-server index 3a3f4d50..7757229e 100755 --- a/dev/u-wave-dev-server +++ b/dev/u-wave-dev-server @@ -32,7 +32,7 @@ const testTransport = { * üWave API development server. */ async function start() { - const port = Number(argv.port || process.env.PORT || 6042); + const port = Number(argv.port ?? process.env.PORT ?? 6042); const uwave = require('..'); diff --git a/example/index.js b/example/index.js index 0e1d88ce..597a70bb 100644 --- a/example/index.js +++ b/example/index.js @@ -8,15 +8,15 @@ import dotenv from 'dotenv'; dotenv.config(); -const port = process.env.PORT || 80; +const port = process.env.PORT ?? 80; const secret = Buffer.from(process.env.SECRET, 'hex'); const DEFAULT_MONGO_URL = 'mongodb://localhost:27017/uwave'; const DEFAULT_REDIS_URL = 'redis://localhost:6379'; const uw = uwave({ - mongo: process.env.MONGO_CONNECTION_URL || DEFAULT_MONGO_URL, - redis: process.env.REDIS_CONNECTION_URL || DEFAULT_REDIS_URL, + mongo: process.env.MONGO_CONNECTION_URL ?? DEFAULT_MONGO_URL, + redis: process.env.REDIS_CONNECTION_URL ?? DEFAULT_REDIS_URL, port, secret, // This has to be disabled so the headers do not get added to the web client too. diff --git a/package.json b/package.json index 2af4c82a..c8f61687 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "u-wave-core": "bin/u-wave-core" }, "engines": { - "node": ">= 12.20.0" + "node": ">= 14.17.0" }, "dependencies": { "ajv": "^8.0.5", @@ -34,7 +34,6 @@ "cookie": "^0.5.0", "cookie-parser": "^1.4.4", "cors": "^2.8.5", - "crypto-randomuuid": "^1.0.0", "debug": "^4.1.1", "escape-string-regexp": "^4.0.0", "explain-error": "^1.0.4", @@ -71,10 +70,7 @@ "yaml": "^2.0.0" }, "devDependencies": { - "@babel/core": "^7.13.13", - "@babel/eslint-parser": "^7.13.10", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.1", "@types/bcryptjs": "^2.4.2", "@types/cookie": "^0.5.0", "@types/cookie-parser": "^1.4.2", @@ -88,7 +84,7 @@ "@types/jsonwebtoken": "^8.5.1", "@types/lodash": "^4.14.168", "@types/ms": "^0.7.31", - "@types/node": "12", + "@types/node": "14", "@types/node-fetch": "^2.5.8", "@types/nodemailer": "^6.4.1", "@types/passport": "^1.0.6", diff --git a/src/HttpApi.js b/src/HttpApi.js index 43ec56f0..ac3c722c 100644 --- a/src/HttpApi.js +++ b/src/HttpApi.js @@ -113,7 +113,7 @@ async function httpApi(uw, options) { mailTransport: options.mailTransport, recaptcha: options.recaptcha, createPasswordResetEmail: - options.createPasswordResetEmail || defaultCreatePasswordResetEmail, + options.createPasswordResetEmail ?? defaultCreatePasswordResetEmail, })) .use('/bans', bans()) .use('/import', imports()) diff --git a/src/Page.js b/src/Page.js index f394ad3e..a12285da 100644 --- a/src/Page.js +++ b/src/Page.js @@ -24,7 +24,7 @@ class Page { } get pageSize() { - return this.opts.pageSize || this.length; + return this.opts.pageSize ?? this.length; } get filteredSize() { diff --git a/src/Source.js b/src/Source.js index 2030bf02..e04f51cc 100644 --- a/src/Source.js +++ b/src/Source.js @@ -94,7 +94,7 @@ class Source { } get apiVersion() { - return this.plugin.api || 1; + return this.plugin.api ?? 1; } /** diff --git a/src/auth/JWTStrategy.js b/src/auth/JWTStrategy.js index 6f0a499c..7337d3f8 100644 --- a/src/auth/JWTStrategy.js +++ b/src/auth/JWTStrategy.js @@ -77,8 +77,8 @@ class JWTStrategy extends Strategy { const { bans } = req.uwave; const token = getQueryToken(req.query) - || getHeaderToken(req.headers) - || getCookieToken(req.cookies); + ?? getHeaderToken(req.headers) + ?? getCookieToken(req.cookies); if (!token) { return this.pass(); } diff --git a/src/controllers/authenticate.js b/src/controllers/authenticate.js index 5b79e1c4..8f6116b4 100644 --- a/src/controllers/authenticate.js +++ b/src/controllers/authenticate.js @@ -46,7 +46,7 @@ function seconds(str) { * @type {import('../types').Controller} */ async function getCurrentUser(req) { - return toItemResponse(req.user || null, { + return toItemResponse(req.user ?? null, { url: req.fullUrl, }); } @@ -84,7 +84,7 @@ async function refreshSession(res, api, user, options) { const serialized = cookie.serialize('uwsession', token, { httpOnly: true, secure: !!options.cookieSecure, - path: options.cookiePath || '/', + path: options.cookiePath ?? '/', maxAge: seconds('31 days'), }); res.setHeader('Set-Cookie', serialized); @@ -445,7 +445,7 @@ async function logout(req, res) { const serialized = cookie.serialize('uwsession', '', { httpOnly: true, secure: !!cookieSecure, - path: cookiePath || '/', + path: cookiePath ?? '/', maxAge: 0, }); res.setHeader('Set-Cookie', serialized); diff --git a/src/controllers/now.js b/src/controllers/now.js index 562a0ebc..310393e7 100644 --- a/src/controllers/now.js +++ b/src/controllers/now.js @@ -80,7 +80,7 @@ async function getState(req) { const booth = getBoothData(uw); const waitlist = uw.waitlist.getUserIDs(); const waitlistLocked = uw.waitlist.isLocked(); - let activePlaylist = user && user.activePlaylist + let activePlaylist = user?.activePlaylist ? uw.playlists.getUserPlaylist(user, user.activePlaylist) : null; const playlists = user ? uw.playlists.getUserPlaylists(user) : null; @@ -91,7 +91,7 @@ async function getState(req) { if (activePlaylist != null) { activePlaylist = activePlaylist - .then((playlist) => (playlist ? playlist.id : null)) + .then((playlist) => playlist?.id) .catch((err) => { // If the playlist was not found, our database is inconsistent. A deleted or nonexistent // playlist should never be listed as the active playlist. Most likely this is not the diff --git a/src/controllers/playlists.js b/src/controllers/playlists.js index 19bf15ad..8139caab 100644 --- a/src/controllers/playlists.js +++ b/src/controllers/playlists.js @@ -222,7 +222,7 @@ async function getPlaylistItems(req) { const { user } = req; const { playlists } = req.uwave; const { id } = req.params; - const filter = req.query.filter || undefined; + const filter = req.query.filter ?? undefined; const pagination = getOffsetPagination(req.query); const playlist = await playlists.getUserPlaylist(user, new ObjectId(id)); diff --git a/src/controllers/server.js b/src/controllers/server.js index a8eba32e..cf7acdaf 100644 --- a/src/controllers/server.js +++ b/src/controllers/server.js @@ -37,7 +37,7 @@ async function getConfig(req) { const combinedSchema = config.getSchema(); const schema = combinedSchema.properties[key]; - return toItemResponse(values || {}, { + return toItemResponse(values ?? {}, { url: req.fullUrl, meta: includeSchema ? { schema } : {}, }); diff --git a/src/email.js b/src/email.js index 2f23c7cc..f8517fff 100644 --- a/src/email.js +++ b/src/email.js @@ -19,7 +19,7 @@ async function sendEmail(emailAddress, options) { }, }; - const transporter = nodemailer.createTransport(options.mailTransport || smtpOptions); + const transporter = nodemailer.createTransport(options.mailTransport ?? smtpOptions); const mailOptions = { to: emailAddress, ...options.email }; diff --git a/src/middleware/errorHandler.js b/src/middleware/errorHandler.js index 0a10945d..f950875b 100644 --- a/src/middleware/errorHandler.js +++ b/src/middleware/errorHandler.js @@ -41,8 +41,8 @@ function serializeError(err) { if (err instanceof APIError) { return [{ - status: err.status || 500, - code: err.code || 'api-error', + status: err.status ?? 500, + code: err.code ?? 'api-error', title: err.message, }]; } @@ -63,7 +63,7 @@ function serializeError(err) { if (err.expose) { /** @type {SerializedError} */ const apiError = { - status: err.status || 400, + status: err.status ?? 400, code: err.code, title: err.message, }; diff --git a/src/middleware/rateLimit.js b/src/middleware/rateLimit.js index 7203a2a6..b827dfe5 100644 --- a/src/middleware/rateLimit.js +++ b/src/middleware/rateLimit.js @@ -16,7 +16,7 @@ RateLimiter.prototype.getAsync = promisify(RateLimiter.prototype.get); * @returns {import('express').RequestHandler} */ function rateLimit(prefix, opts) { - const RLError = opts.error || RateLimitError; + const RLError = opts.error ?? RateLimitError; return wrapMiddleware(async (req, res) => { const uw = req.uwave; diff --git a/src/modules.d.ts b/src/modules.d.ts deleted file mode 100644 index 5bd7b270..00000000 --- a/src/modules.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Contains typings for dependencies that do not have types. - -declare module 'crypto-randomuuid' { - // From https://github.com/DefinitelyTyped/DefinitelyTyped/blob/04843fe4de7d03161bf6e5f2b51c49b2cd21e96c/types/node/crypto.d.ts#L3019-L3035 - - interface RandomUUIDOptions { - /** - * By default, to improve performance, - * Node.js will pre-emptively generate and persistently cache enough - * random data to generate up to 128 random UUIDs. To generate a UUID - * without using the cache, set `disableEntropyCache` to `true`. - * - * @default `false` - */ - disableEntropyCache?: boolean | undefined; - } - /** - * Generates a random [RFC 4122](https://www.rfc-editor.org/rfc/rfc4122.txt) version 4 UUID. The UUID is generated using a - * cryptographic pseudorandom number generator. - */ - function randomUUID(options?: RandomUUIDOptions): string; - export = randomUUID; -} diff --git a/src/plugins/bans.js b/src/plugins/bans.js index f5ce150f..4ce13e24 100644 --- a/src/plugins/bans.js +++ b/src/plugins/bans.js @@ -58,7 +58,7 @@ class Bans { async getBans(filter, pagination = {}) { const { User } = this.#uw.models; - const offset = pagination.offset || 0; + const offset = pagination.offset ?? 0; const size = clamp( pagination.limit == null ? 50 : pagination.limit, 0, diff --git a/src/plugins/chat.js b/src/plugins/chat.js index 29c39961..2a173b5d 100644 --- a/src/plugins/chat.js +++ b/src/plugins/chat.js @@ -1,6 +1,6 @@ 'use strict'; -const randomUUID = require('crypto-randomuuid'); +const { randomUUID } = require('crypto'); const routes = require('../routes/chat'); /** diff --git a/src/plugins/configStore.js b/src/plugins/configStore.js index 5cd2c023..5327195e 100644 --- a/src/plugins/configStore.js +++ b/src/plugins/configStore.js @@ -100,7 +100,7 @@ class ConfigStore { const validate = this.#registry.get(key); if (!validate) return undefined; - const config = (await this.load(key)) || {}; + const config = (await this.load(key)) ?? {}; // Allowed to fail--just fills in defaults validate(config); diff --git a/src/plugins/history.js b/src/plugins/history.js index 1f45d6b9..942d5ae6 100644 --- a/src/plugins/history.js +++ b/src/plugins/history.js @@ -36,7 +36,7 @@ class HistoryRepository { async getHistory(filter, pagination = {}) { const { HistoryEntry } = this.#uw.models; - const offset = pagination.offset || 0; + const offset = pagination.offset ?? 0; const limit = clamp( typeof pagination.limit === 'number' ? pagination.limit : DEFAULT_PAGE_SIZE, 0, diff --git a/src/plugins/playlists.js b/src/plugins/playlists.js index 38525bba..1671daa9 100644 --- a/src/plugins/playlists.js +++ b/src/plugins/playlists.js @@ -75,8 +75,8 @@ function toPlaylistItem(itemProps, media) { const { start, end } = getStartEnd(itemProps, media); return { media, - artist: artist || media.artist, - title: title || media.title, + artist: artist ?? media.artist, + title: title ?? media.title, start, end, }; diff --git a/src/plugins/waitlist.js b/src/plugins/waitlist.js index ede2fb6f..259b8db2 100644 --- a/src/plugins/waitlist.js +++ b/src/plugins/waitlist.js @@ -215,7 +215,7 @@ class Waitlist { } const clampedPosition = clamp(position, 0, waitlist.length); - const beforeID = waitlist[clampedPosition] || null; + const beforeID = waitlist[clampedPosition] ?? null; if (beforeID === user.id) { // No change. diff --git a/src/sockets/AuthedConnection.js b/src/sockets/AuthedConnection.js index c358f47c..353aead9 100644 --- a/src/sockets/AuthedConnection.js +++ b/src/sockets/AuthedConnection.js @@ -71,7 +71,7 @@ class AuthedConnection extends EventEmitter { * @private */ onMessage(raw) { - const { command, data } = sjson.safeParse(raw) || {}; + const { command, data } = sjson.safeParse(raw) ?? {}; if (command) { this.emit('command', command, data); } diff --git a/test/utils/createUwave.mjs b/test/utils/createUwave.mjs index ad82fa45..193c6a78 100644 --- a/test/utils/createUwave.mjs +++ b/test/utils/createUwave.mjs @@ -7,7 +7,7 @@ import uwave from 'u-wave-core'; import deleteDatabase from './deleteDatabase.mjs'; import testPlugin from './plugin.mjs'; -const DB_HOST = process.env.MONGODB_HOST || 'localhost'; +const DB_HOST = process.env.MONGODB_HOST ?? 'localhost'; /** * Create a separate in-memory redis instance to run tests against. @@ -46,7 +46,7 @@ async function createIsolatedRedis() { * This can be used to run tests on CI. */ function createRedisConnection() { - const url = process.env.REDIS_URL || 'redis://localhost:6379'; + const url = process.env.REDIS_URL ?? 'redis://localhost:6379'; async function close() { const redis = new Redis(url); diff --git a/tsconfig.json b/tsconfig.json index 3f433587..29fc3eb2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@tsconfig/node12/tsconfig.json", + "extends": "@tsconfig/node14/tsconfig.json", "compilerOptions": { "strict": true, "useUnknownInCatchVariables": false,