From 4e1b0bd72c822e01c0854b002aa6ac938d2340ab Mon Sep 17 00:00:00 2001 From: Alexis Tyler Date: Thu, 28 Jan 2021 15:45:14 +1030 Subject: [PATCH] chore: lint --- .eslintrc.js | 7 + app/cache/index.ts | 2 +- app/cache/user.ts | 32 +-- app/consts.ts | 10 +- app/core/api-manager.ts | 15 +- app/core/core.ts | 26 +- app/core/default-permissions.ts | 100 ++++---- app/core/discovery/announce.ts | 2 +- app/core/errors/api-key-error.ts | 6 +- app/core/log.ts | 8 +- app/core/modules/add-apikey.ts | 2 +- app/core/modules/add-license-key.ts | 6 +- app/core/modules/add-plugin.ts | 2 +- app/core/modules/add-share.ts | 2 +- app/core/modules/add-user.ts | 2 +- .../modules/apikeys/name/delete-apikey.ts | 2 +- app/core/modules/apikeys/name/get-apikey.ts | 4 +- .../modules/apikeys/name/update-apikey.ts | 2 +- app/core/modules/array/add-disk-to-array.ts | 2 +- .../modules/array/remove-disk-from-array.ts | 2 +- app/core/modules/array/update-array.ts | 2 +- app/core/modules/array/update-parity-check.ts | 6 +- app/core/modules/disks/id/get-disk.ts | 2 +- .../modules/docker/get-docker-containers.ts | 4 +- .../modules/docker/get-docker-networks.ts | 2 +- app/core/modules/get-all-shares.ts | 2 +- app/core/modules/get-apikeys.ts | 2 +- app/core/modules/get-apps.ts | 2 +- app/core/modules/get-devices.ts | 2 +- app/core/modules/get-disks.ts | 6 +- app/core/modules/get-parity-history.ts | 2 +- app/core/modules/get-permissions.ts | 6 +- app/core/modules/get-plugins.ts | 2 +- app/core/modules/get-services.ts | 2 +- app/core/modules/get-unassigned-devices.ts | 2 +- app/core/modules/get-uptime.ts | 4 +- app/core/modules/get-users.ts | 2 +- app/core/modules/get-vars.ts | 2 +- app/core/modules/get-welcome.ts | 2 +- app/core/modules/info/get-all-devices.ts | 12 +- app/core/modules/info/get-app-count.ts | 2 +- app/core/modules/info/get-baseboard.ts | 2 +- app/core/modules/info/get-cpu.ts | 2 +- app/core/modules/info/get-display.ts | 2 +- app/core/modules/info/get-machine-id.ts | 2 +- app/core/modules/info/get-memory.ts | 2 +- app/core/modules/info/get-os.ts | 2 +- .../modules/info/get-software-versions.ts | 2 +- app/core/modules/info/get-unraid-version.ts | 6 +- app/core/modules/info/get-versions.ts | 2 +- app/core/modules/services/get-emhttpd.ts | 2 +- app/core/modules/services/get-unraid-api.ts | 4 +- app/core/modules/settings/update-disk.ts | 4 +- app/core/modules/shares/name/get-share.ts | 2 +- app/core/modules/users/id/add-role.ts | 2 +- app/core/modules/users/id/delete-user.ts | 2 +- app/core/modules/users/id/get-user.ts | 2 +- .../modules/vms/domains/domain/get-domain.ts | 2 +- app/core/modules/vms/get-domains.ts | 2 +- app/core/notifiers/email.ts | 8 +- app/core/notifiers/http.ts | 2 +- app/core/notifiers/notifier.ts | 2 +- app/core/notifiers/unraid.ts | 2 +- app/core/permission-manager.ts | 4 +- app/core/permissions.ts | 12 +- app/core/plugin-manager.ts | 2 +- app/core/states/network.ts | 64 +++-- app/core/states/smb-sec.ts | 2 +- app/core/states/state.ts | 9 +- app/core/types/global.ts | 16 +- app/core/types/states/var.ts | 2 +- app/core/utils/authentication/check-auth.ts | 2 +- app/core/utils/authentication/ensure-auth.ts | 2 +- app/core/utils/clients/emcmd.ts | 2 +- app/core/utils/clients/nchan.ts | 10 +- app/core/utils/misc/atomic-sleep.ts | 2 +- app/core/utils/misc/catch-handlers.ts | 2 +- app/core/utils/misc/get-endpoints.ts | 6 +- app/core/utils/misc/get-machine-id.ts | 2 +- app/core/utils/misc/get-node-service.ts | 238 +++++++++--------- app/core/utils/misc/load-state.ts | 2 +- app/core/utils/misc/parse-config.ts | 12 +- app/core/utils/misc/sleep.ts | 2 +- .../utils/misc/validate-api-key-format.ts | 8 +- app/core/utils/misc/validate-api-key.ts | 6 +- app/core/utils/permissions/get-permissions.ts | 4 +- app/core/utils/plugins/php-loader.ts | 2 +- app/core/utils/upcast.ts | 11 +- app/core/utils/vms/filter-devices.ts | 4 +- app/core/utils/vms/get-hypervisor.ts | 2 +- app/core/utils/vms/get-pci-devices.ts | 2 +- app/core/utils/vms/parse-domain.ts | 4 +- app/core/utils/vms/parse-domains.ts | 4 +- app/core/utils/write-to-boot.ts | 12 +- app/core/watchers/plugins.ts | 4 +- app/core/watchers/states.ts | 60 ++--- app/graphql/index.ts | 28 +-- app/graphql/resolvers/index.ts | 8 +- app/graphql/resolvers/query/display.ts | 128 +++++----- app/graphql/resolvers/query/index.ts | 12 +- app/graphql/resolvers/query/info.ts | 2 +- app/graphql/resolvers/query/online.ts | 2 +- app/graphql/resolvers/query/server.ts | 16 +- app/graphql/resolvers/query/servers.ts | 18 +- app/graphql/resolvers/query/vms.ts | 2 +- app/graphql/resolvers/subscription/index.ts | 148 +++++------ app/graphql/resolvers/user-account.ts | 16 +- app/graphql/schema/utils.ts | 4 +- app/index.ts | 12 +- app/mothership/custom-socket.ts | 202 +++++++-------- app/mothership/index.ts | 4 +- app/mothership/sockets/internal-graphql.ts | 23 +- app/mothership/sockets/mothership.ts | 178 ++++++------- app/mothership/subscribe-to-servers.ts | 96 +++---- app/mothership/utils.ts | 4 +- app/run.ts | 95 +++---- app/server.ts | 34 +-- app/utils.ts | 47 ++-- app/ws.ts | 66 ++--- package.json | 1 + tsconfig.json | 2 +- 121 files changed, 1012 insertions(+), 1012 deletions(-) create mode 100644 .eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..bf47efd6e --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + extends: [ + '@unraid' + ], +}; \ No newline at end of file diff --git a/app/cache/index.ts b/app/cache/index.ts index 4d8c0a60f..e5abc8565 100644 --- a/app/cache/index.ts +++ b/app/cache/index.ts @@ -1 +1 @@ -export * from './user'; \ No newline at end of file +export * from './user'; diff --git a/app/cache/user.ts b/app/cache/user.ts index 9fdb835d3..7f18ad3d2 100644 --- a/app/cache/user.ts +++ b/app/cache/user.ts @@ -7,23 +7,23 @@ type IpAddress = string; type Status = 'online' | 'offline'; export interface Owner { - username: string; - url: URL; - avatar: URL; -}; + username: string; + url: URL; + avatar: URL; +} export interface CachedServer { - owner: Owner; - guid: string; - apikey: string; - name: string; - status: Status; - wanip: IpAddress; - lanip: IpAddress; - localurl: URL; - remoteurl: string -}; + owner: Owner; + guid: string; + apikey: string; + name: string; + status: Status; + wanip: IpAddress; + lanip: IpAddress; + localurl: URL; + remoteurl: string; +} export interface CachedServers { - servers: CachedServer[] -}; \ No newline at end of file + servers: CachedServer[]; +} diff --git a/app/consts.ts b/app/consts.ts index 27553a419..7cf9a4c7b 100644 --- a/app/consts.ts +++ b/app/consts.ts @@ -1,13 +1,13 @@ import { config } from './core/config'; const internalWsAddress = () => { - const port = config.get('port'); - return isNaN(port as any) + const port = config.get('port') as number | string; + return isNaN(port as any) ? // Unix Socket - ? `ws+unix:${port}` + `ws+unix:${port}` : // Numbered port - : `ws://localhost:${port}`; -} + `ws://localhost:${port}`; +}; /** * One second in milliseconds. diff --git a/app/core/api-manager.ts b/app/core/api-manager.ts index 60228ba4b..8077fbf97 100644 --- a/app/core/api-manager.ts +++ b/app/core/api-manager.ts @@ -9,7 +9,6 @@ import { EventEmitter } from 'events'; import toMillisecond from 'ms'; import dotProp from 'dot-prop'; import { Cache as MemoryCache } from 'clean-cache'; -// @ts-ignore import { validate as validateArgument } from 'bycontract'; import { Mutex, MutexInterface } from 'async-mutex'; import { validateApiKeyFormat, loadState, validateApiKey } from './utils'; @@ -53,7 +52,7 @@ export class ApiManager extends EventEmitter { /** Note: Keys expire by default after 365 days. */ private readonly keys = new MemoryCache(Number(toMillisecond('1y'))); - + private lock?: MutexInterface; private async getLock() { if (!this.lock) { @@ -70,11 +69,11 @@ export class ApiManager extends EventEmitter { const lock = await this.getLock(); try { const file = loadState<{ remote: { apikey: string } }>(filePath); - const apiKey = dotProp.get(file, 'remote.apikey') as string; + const apiKey = dotProp.get(file, 'remote.apikey')! as string; // Same key as current if (!force && (apiKey === this.getKey('my_servers')?.key)) { - coreLogger.debug(`%s was updated but the API key didn't change`, filePath); + coreLogger.debug('%s was updated but the API key didn\'t change', filePath); return; } @@ -136,12 +135,12 @@ export class ApiManager extends EventEmitter { /** * Replace a key. - * + * * Note: This will bump the expiration by the original length. */ replace(name: string, key: string, options: KeyOptions) { // Delete existing key - // @ts-ignore + // @ts-expect-error this.keys.items[name] = null; // Add new key @@ -269,7 +268,7 @@ export class ApiManager extends EventEmitter { .filter(([, item]) => this.isValid(item.value.key)) .map(([name, item]) => ({ name, - // @ts-ignore + // @ts-expect-error key: item.value.key, userId: item.value.userId, expiresAt: item.expiresAt @@ -288,7 +287,7 @@ export class ApiManager extends EventEmitter { const keyObject = Object .entries(this.keys.items) - // @ts-ignore + // @ts-expect-error .find(([_, item]) => item.value.key === key); if (!keyObject) { diff --git a/app/core/core.ts b/app/core/core.ts index 6b0b7ab87..97a1c63cf 100644 --- a/app/core/core.ts +++ b/app/core/core.ts @@ -40,7 +40,7 @@ const loadingLogger = (namespace: string): void => { /** * Register state paths. */ -const loadStatePaths = async(): Promise => { +const loadStatePaths = async (): Promise => { const statesCwd = paths.get('states')!; const cwd = path.join(__dirname, 'states'); @@ -52,10 +52,10 @@ const loadStatePaths = async(): Promise => { const filePath = `${path.join(statesCwd, state)}.ini`; // Don't override already set paths - // @ts-ignore + // @ts-expect-error if (!paths.has(name)) { // ['state:Users', '/usr/local/emhttp/state/users.ini'] - // @ts-ignore + // @ts-expect-error paths.set(name, filePath); } }); @@ -64,7 +64,7 @@ const loadStatePaths = async(): Promise => { /** * Register all plugins with PluginManager. */ -const loadPlugins = async(): Promise => { +const loadPlugins = async (): Promise => { // Bail in safe mode if (config.get('safe-mode')) { coreLogger.debug('No plugins have been loaded as you\'re in SAFE MODE'); @@ -114,7 +114,7 @@ const loadPlugins = async(): Promise => { /** * Start all watchers. */ -const loadWatchers = async(): Promise => { +const loadWatchers = async (): Promise => { if (config.get('safe-mode')) { coreLogger.debug('Skipping loading watchers'); return; @@ -136,7 +136,7 @@ const loadWatchers = async(): Promise => { * @async * @private */ -const loadApiKeys = async(): Promise => { +const loadApiKeys = async (): Promise => { // @TODO: For each key in a json file load them }; @@ -145,7 +145,7 @@ const loadApiKeys = async(): Promise => { * * @param endpoints */ -const connectToNchanEndpoints = async(endpoints: string[]): Promise => { +const connectToNchanEndpoints = async (endpoints: string[]): Promise => { coreLogger.debug('Connected to nchan, setting-up endpoints.'); const connections = endpoints.map(async endpoint => subscribeToNchanEndpoint(endpoint)); await Promise.all(connections); @@ -158,7 +158,7 @@ const connectToNchanEndpoints = async(endpoints: string[]): Promise => { * @async * @private */ -const loadNchan = async(): Promise => { +const loadNchan = async (): Promise => { const endpoints = ['devs', 'disks', 'sec', 'sec_nfs', 'shares', 'users', 'var']; coreLogger.debug('Trying to connect to nchan'); @@ -169,7 +169,7 @@ const loadNchan = async(): Promise => { interval: ONE_SECOND }) // Once connected open a connection to each known endpoint - .then(async() => connectToNchanEndpoints(endpoints)) + .then(async () => connectToNchanEndpoints(endpoints)) .catch(error => { // Nchan is likely unreachable if (error.message.includes('Promise timed out')) { @@ -196,7 +196,7 @@ const loaders = { * * @name core.load */ -const load = async(): Promise => { +const load = async (): Promise => { await loadStatePaths(); await loadPlugins(); await loadWatchers(); @@ -214,7 +214,7 @@ const load = async(): Promise => { * @name core.loadServer * @param name The name of the server instance to load. */ -export const loadServer = async(name: string, server: typeof Server): Promise => { +export const loadServer = async (name: string, server: typeof Server): Promise => { // Set process title process.title = name; @@ -232,7 +232,7 @@ export const loadServer = async(name: string, server: typeof Server): Promise { + exitHook(async () => { // Only do this when there's a TTY present if (process.stdout.isTTY) { // Ensure we go back to the start of the line @@ -241,7 +241,7 @@ export const loadServer = async(name: string, server: typeof Server): Promise => { +export const announce = async (): Promise => { const name = varState.data?.name; const localTld = varState.data?.localTld; const version = varState.data?.version; diff --git a/app/core/errors/api-key-error.ts b/app/core/errors/api-key-error.ts index 2b5a93dee..93df31a82 100644 --- a/app/core/errors/api-key-error.ts +++ b/app/core/errors/api-key-error.ts @@ -9,7 +9,7 @@ import { AppError } from './app-error'; * API key error. */ export class ApiKeyError extends AppError { - constructor(message: string) { - super(message); - } + constructor(message: string) { + super(message); + } } diff --git a/app/core/log.ts b/app/core/log.ts index dfd357376..1beed548d 100644 --- a/app/core/log.ts +++ b/app/core/log.ts @@ -7,7 +7,7 @@ import { Logger } from 'logger'; export const log = new Logger({ prefix: '@unraid' }); export const coreLogger = log.createChild({ prefix: 'core' }); -export const mothershipLogger = log.createChild({ prefix: 'mothership'}); -export const graphqlLogger = log.createChild({ prefix: 'graphql'}); -export const relayLogger = log.createChild({ prefix: 'relay'}); -export const discoveryLogger = log.createChild({ prefix: 'discovery'}); \ No newline at end of file +export const mothershipLogger = log.createChild({ prefix: 'mothership' }); +export const graphqlLogger = log.createChild({ prefix: 'graphql' }); +export const relayLogger = log.createChild({ prefix: 'relay' }); +export const discoveryLogger = log.createChild({ prefix: 'discovery' }); diff --git a/app/core/modules/add-apikey.ts b/app/core/modules/add-apikey.ts index 52198a597..40695627d 100644 --- a/app/core/modules/add-apikey.ts +++ b/app/core/modules/add-apikey.ts @@ -27,7 +27,7 @@ interface Result extends CoreResult { * * NOTE: If the name or key is missing they'll be generated. */ -export const addApikey = async(context: Context): Promise => { +export const addApikey = async (context: Context): Promise => { ensurePermission(context.user, { resource: 'apikey', action: 'create', diff --git a/app/core/modules/add-license-key.ts b/app/core/modules/add-license-key.ts index 94647a9c1..8555dff89 100644 --- a/app/core/modules/add-license-key.ts +++ b/app/core/modules/add-license-key.ts @@ -31,16 +31,16 @@ interface Result extends CoreResult { /** * Register a license key. */ -export const addLicenseKey = async(context: Context): Promise => { +export const addLicenseKey = async (context: Context): Promise => { ensurePermission(context.user, { resource: 'license-key', action: 'create', possession: 'any' }); - // const { data } = context; + // Const { data } = context; const guid = varState?.data?.regGuid; - // const timestamp = new Date(); + // Const timestamp = new Date(); if (!guid) { throw new AppError('guid missing'); diff --git a/app/core/modules/add-plugin.ts b/app/core/modules/add-plugin.ts index 67ae05386..3a2d6ba32 100644 --- a/app/core/modules/add-plugin.ts +++ b/app/core/modules/add-plugin.ts @@ -25,7 +25,7 @@ interface Context extends CoreContext { * Install plugin. * @returns The newly installed plugin. */ -export const addPlugin = async(context: Context): Promise => { +export const addPlugin = async (context: Context): Promise => { // Check permissions ensurePermission(context.user, { resource: 'plugin', diff --git a/app/core/modules/add-share.ts b/app/core/modules/add-share.ts index e23ed84d1..9b697530a 100644 --- a/app/core/modules/add-share.ts +++ b/app/core/modules/add-share.ts @@ -8,7 +8,7 @@ import { AppError, NotImplementedError } from '../errors'; import { sharesState, slotsState } from '../states'; import { ensurePermission } from '../utils'; -export const addShare = async(context: CoreContext): Promise => { +export const addShare = async (context: CoreContext): Promise => { const { user, data = {} } = context; // Check permissions diff --git a/app/core/modules/add-user.ts b/app/core/modules/add-user.ts index cad832366..b2cb763c9 100644 --- a/app/core/modules/add-user.ts +++ b/app/core/modules/add-user.ts @@ -23,7 +23,7 @@ interface Context extends CoreContext { /** * Add user account. */ -export const addUser = async(context: Context): Promise => { +export const addUser = async (context: Context): Promise => { const { data } = context; // Check permissions diff --git a/app/core/modules/apikeys/name/delete-apikey.ts b/app/core/modules/apikeys/name/delete-apikey.ts index 29ea465f6..812c6222a 100644 --- a/app/core/modules/apikeys/name/delete-apikey.ts +++ b/app/core/modules/apikeys/name/delete-apikey.ts @@ -19,6 +19,6 @@ interface Context extends CoreContext { * Invalidate an apiKey. * @returns The deleted apikey. */ -export const deleteApikey = async(_: Context): Promise => { +export const deleteApikey = async (_: Context): Promise => { throw new NotImplementedError(); }; diff --git a/app/core/modules/apikeys/name/get-apikey.ts b/app/core/modules/apikeys/name/get-apikey.ts index 2110b3de7..f4139063b 100644 --- a/app/core/modules/apikeys/name/get-apikey.ts +++ b/app/core/modules/apikeys/name/get-apikey.ts @@ -40,7 +40,7 @@ interface Context extends CoreContext { * @param {string} context.params.name * @returns {Core~Result} The API key, the user who owns the key, when the key expires and the scopes the key can use. */ -export const getApikey = async(context: Context): Promise => { +export const getApikey = async (context: Context): Promise => { const { params, user } = context; const { name } = params; @@ -76,7 +76,7 @@ export const getApikey = async(context: Context): Promise => { const apiKey = apiManager.getKey(name)?.key; if (!apiKey) { - throw new AppError(`A key under this name hasn't been issued or it has expired.`); + throw new AppError('A key under this name hasn\'t been issued or it has expired.'); } return { diff --git a/app/core/modules/apikeys/name/update-apikey.ts b/app/core/modules/apikeys/name/update-apikey.ts index e1f986d23..89d7464be 100644 --- a/app/core/modules/apikeys/name/update-apikey.ts +++ b/app/core/modules/apikeys/name/update-apikey.ts @@ -21,7 +21,7 @@ interface Context extends CoreContext { * * @returns The apikey, when the key expires and the scopes the key can use. */ -export const updateApiKey = async(context: Context): Promise => { +export const updateApiKey = async (context: Context): Promise => { // Since we pass the context we don't need to worry about checking if the user has permissions const key = await getApikey(context).then(result => result.json); diff --git a/app/core/modules/array/add-disk-to-array.ts b/app/core/modules/array/add-disk-to-array.ts index 819365f38..db9afcf51 100644 --- a/app/core/modules/array/add-disk-to-array.ts +++ b/app/core/modules/array/add-disk-to-array.ts @@ -11,7 +11,7 @@ import { getArray } from '..'; /** * Add a disk to the array. */ -export const addDiskToArray = async function(context: CoreContext): Promise { +export const addDiskToArray = async function (context: CoreContext): Promise { const { data = {}, user } = context; // Check permissions diff --git a/app/core/modules/array/remove-disk-from-array.ts b/app/core/modules/array/remove-disk-from-array.ts index e1202ecfa..706230f3b 100644 --- a/app/core/modules/array/remove-disk-from-array.ts +++ b/app/core/modules/array/remove-disk-from-array.ts @@ -19,7 +19,7 @@ interface Context extends CoreContext { * Remove a disk from the array. * @returns The updated array. */ -export const removeDiskFromArray = async(context: Context): Promise => { +export const removeDiskFromArray = async (context: Context): Promise => { const { data, user } = context; // Check permissions diff --git a/app/core/modules/array/update-array.ts b/app/core/modules/array/update-array.ts index b511dd296..472de1e43 100644 --- a/app/core/modules/array/update-array.ts +++ b/app/core/modules/array/update-array.ts @@ -13,7 +13,7 @@ import { getArray } from '..'; // ideally this should have a timeout to prevent it sticking let locked = false; -export const updateArray = async(context: CoreContext): Promise => { +export const updateArray = async (context: CoreContext): Promise => { const { data = {}, user } = context; // Check permissions diff --git a/app/core/modules/array/update-parity-check.ts b/app/core/modules/array/update-parity-check.ts index 07f95a881..a372c5d1e 100644 --- a/app/core/modules/array/update-parity-check.ts +++ b/app/core/modules/array/update-parity-check.ts @@ -12,9 +12,9 @@ type State = 'start' | 'cancel' | 'resume' | 'cancel'; interface Context extends CoreContext { data: { - state?: State - correct?: boolean - } + state?: State; + correct?: boolean; + }; } /** diff --git a/app/core/modules/disks/id/get-disk.ts b/app/core/modules/disks/id/get-disk.ts index 996488338..5f89e5859 100644 --- a/app/core/modules/disks/id/get-disk.ts +++ b/app/core/modules/disks/id/get-disk.ts @@ -16,7 +16,7 @@ interface Context extends CoreContext { /** * Get a single disk. */ -export const getDisk = async(context: Context, Disks): Promise => { +export const getDisk = async (context: Context, Disks): Promise => { const { params, user } = context; // Check permissions diff --git a/app/core/modules/docker/get-docker-containers.ts b/app/core/modules/docker/get-docker-containers.ts index f7a2d50a7..154f5bcb2 100644 --- a/app/core/modules/docker/get-docker-containers.ts +++ b/app/core/modules/docker/get-docker-containers.ts @@ -19,7 +19,7 @@ interface Context extends CoreContext { * Get all Docker containers. * @returns All the in/active Docker containers on the system. */ -export const getDockerContainers = async(context: Context): Promise => { +export const getDockerContainers = async (context: Context): Promise => { const { query, user } = context; const { all } = query; @@ -52,7 +52,7 @@ export const getDockerContainers = async(context: Context): Promise .map(object => camelCaseKeys(object, { deep: true })) .map(container => { // This will be fixed once camelCaseKeys has correct typings - // @ts-ignore + // @ts-expect-error const names = container.names[0]; return { ...container, diff --git a/app/core/modules/docker/get-docker-networks.ts b/app/core/modules/docker/get-docker-networks.ts index c752a3c25..74b2e0552 100644 --- a/app/core/modules/docker/get-docker-networks.ts +++ b/app/core/modules/docker/get-docker-networks.ts @@ -7,7 +7,7 @@ import camelCaseKeys from 'camelcase-keys'; import { docker, catchHandlers, ensurePermission } from '../../utils'; import { CoreContext, CoreResult } from '../../types'; -export const getDockerNetworks = async(context: CoreContext): Promise => { +export const getDockerNetworks = async (context: CoreContext): Promise => { const { user } = context; // Check permissions diff --git a/app/core/modules/get-all-shares.ts b/app/core/modules/get-all-shares.ts index 45af3a815..16a13e3f1 100644 --- a/app/core/modules/get-all-shares.ts +++ b/app/core/modules/get-all-shares.ts @@ -9,7 +9,7 @@ import { getShares, ensurePermission } from '../utils'; /** * Get all shares. */ -export const getAllShares = async(context: CoreContext): Promise => { +export const getAllShares = async (context: CoreContext): Promise => { const { user } = context; // Check permissions diff --git a/app/core/modules/get-apikeys.ts b/app/core/modules/get-apikeys.ts index 7515b6a72..a5edf3064 100644 --- a/app/core/modules/get-apikeys.ts +++ b/app/core/modules/get-apikeys.ts @@ -12,7 +12,7 @@ import { CoreResult, CoreContext } from '../types'; * * @returns All apikeys with their respective `name`, `key` and `expiresAt`. */ -export const getApikeys = async function(context: CoreContext): Promise { +export const getApikeys = async function (context: CoreContext): Promise { const { user } = context; const canReadAny = checkPermission(user, { resource: 'apikey', diff --git a/app/core/modules/get-apps.ts b/app/core/modules/get-apps.ts index 5b9efeee8..76e89b96b 100644 --- a/app/core/modules/get-apps.ts +++ b/app/core/modules/get-apps.ts @@ -8,7 +8,7 @@ import { CoreResult } from '../types'; /** * Get all apps. */ -export const getApps = async(): Promise => { +export const getApps = async (): Promise => { const apps = []; return { diff --git a/app/core/modules/get-devices.ts b/app/core/modules/get-devices.ts index f00f19071..57d76bad2 100644 --- a/app/core/modules/get-devices.ts +++ b/app/core/modules/get-devices.ts @@ -11,7 +11,7 @@ import { ensurePermission } from '../utils'; * Get all devices. * @returns All currently connected devices. */ -export const getDevices = async(context: CoreContext): Promise => { +export const getDevices = async (context: CoreContext): Promise => { const { user } = context; // Check permissions diff --git a/app/core/modules/get-disks.ts b/app/core/modules/get-disks.ts index 86b2d9762..73c31febe 100644 --- a/app/core/modules/get-disks.ts +++ b/app/core/modules/get-disks.ts @@ -22,7 +22,7 @@ interface Disk extends si.Systeminformation.DiskLayoutData { partitions: Partition[]; } -const getTemperature = async(disk: si.Systeminformation.DiskLayoutData): Promise => { +const getTemperature = async (disk: si.Systeminformation.DiskLayoutData): Promise => { const stdout = await execa('smartctl', ['-A', disk.device]).then(({ stdout }) => stdout).catch(() => ''); const lines = stdout.split('\n'); const header = lines.find(line => line.startsWith('ID#'))!; @@ -43,7 +43,7 @@ const getTemperature = async(disk: si.Systeminformation.DiskLayoutData): Promise return Number.parseInt(line[line.length - 1], 10); }; -const parseDisk = async(disk: si.Systeminformation.DiskLayoutData, partitionsToParse: si.Systeminformation.BlockDevicesData[]): Promise => { +const parseDisk = async (disk: si.Systeminformation.DiskLayoutData, partitionsToParse: si.Systeminformation.BlockDevicesData[]): Promise => { const partitions = partitionsToParse // Only get partitions from this disk .filter(partition => partition.name.startsWith(disk.device.split('/dev/')[1])) @@ -70,7 +70,7 @@ interface Result extends CoreResult { /** * Get all disks. */ -export const getDisks = async(context: CoreContext): Promise => { +export const getDisks = async (context: CoreContext): Promise => { const { user } = context; // Check permissions diff --git a/app/core/modules/get-parity-history.ts b/app/core/modules/get-parity-history.ts index 71d759b70..b21bc21c1 100644 --- a/app/core/modules/get-parity-history.ts +++ b/app/core/modules/get-parity-history.ts @@ -15,7 +15,7 @@ const Table = require('cli-table'); * Get parity history. * @returns All parity checks with their respective date, duration, speed, status and errors. */ -export const getParityHistory = async(context: CoreContext): Promise => { +export const getParityHistory = async (context: CoreContext): Promise => { const { user } = context; // Bail if the user doesn't have permission diff --git a/app/core/modules/get-permissions.ts b/app/core/modules/get-permissions.ts index f142fdab2..8db9c8d8f 100644 --- a/app/core/modules/get-permissions.ts +++ b/app/core/modules/get-permissions.ts @@ -10,7 +10,7 @@ import { CoreContext, CoreResult } from '../types'; /** * Get all permissions. */ -export const getPermissions = async function(context: CoreContext): Promise { +export const getPermissions = async function (context: CoreContext): Promise { const { user } = context; // Bail if the user doesn't have permission @@ -22,7 +22,7 @@ export const getPermissions = async function(context: CoreContext): Promise { - // @ts-ignore + // @ts-expect-error const { $extend, ...grants } = grant; return { ...grants, @@ -33,7 +33,7 @@ export const getPermissions = async function(context: CoreContext): Promise { - // @ts-ignore + // @ts-expect-error const { $extend, ...grants } = grant; return [name, grants]; }) diff --git a/app/core/modules/get-plugins.ts b/app/core/modules/get-plugins.ts index c37f21481..fcdb551b6 100644 --- a/app/core/modules/get-plugins.ts +++ b/app/core/modules/get-plugins.ts @@ -16,7 +16,7 @@ interface Context extends CoreContext { } interface Result extends CoreResult { - json: Plugin[] + json: Plugin[]; } export const getPlugins = (context: Readonly): Result => { diff --git a/app/core/modules/get-services.ts b/app/core/modules/get-services.ts index cc1745951..5be39edb7 100644 --- a/app/core/modules/get-services.ts +++ b/app/core/modules/get-services.ts @@ -46,7 +46,7 @@ interface Result extends CoreResult { /** * Get all services. */ -export const getServices = async(context: CoreContext): Promise => { +export const getServices = async (context: CoreContext): Promise => { const logErrorAndReturnEmptyArray = (error: Error) => { coreLogger.error(error); return []; diff --git a/app/core/modules/get-unassigned-devices.ts b/app/core/modules/get-unassigned-devices.ts index 68448e7ec..4536ce184 100644 --- a/app/core/modules/get-unassigned-devices.ts +++ b/app/core/modules/get-unassigned-devices.ts @@ -10,7 +10,7 @@ import { ensurePermission } from '../utils'; /** * Get all unassigned devices. */ -export const getUnassignedDevices = async(context: CoreContext): Promise => { +export const getUnassignedDevices = async (context: CoreContext): Promise => { const { user } = context; // Bail if the user doesn't have permission diff --git a/app/core/modules/get-uptime.ts b/app/core/modules/get-uptime.ts index c15cae9f7..04f35da0e 100644 --- a/app/core/modules/get-uptime.ts +++ b/app/core/modules/get-uptime.ts @@ -11,14 +11,14 @@ interface Result extends CoreResult { json: { milliseconds: number; timestamp: string; - } + }; } /** * OS uptime * @returns The milliseconds since we booted. */ -export const getUptime = async(context: CoreContext): Promise => { +export const getUptime = async (context: CoreContext): Promise => { const { user } = context; // Check permissions diff --git a/app/core/modules/get-users.ts b/app/core/modules/get-users.ts index 61e7c6929..d999baac0 100644 --- a/app/core/modules/get-users.ts +++ b/app/core/modules/get-users.ts @@ -19,7 +19,7 @@ interface Context extends CoreContext { /** * Get all users. */ -export const getUsers = async(context: Context): Promise => { +export const getUsers = async (context: Context): Promise => { const { query, user } = context; // Check permissions diff --git a/app/core/modules/get-vars.ts b/app/core/modules/get-vars.ts index 44dc37388..acbd6f760 100644 --- a/app/core/modules/get-vars.ts +++ b/app/core/modules/get-vars.ts @@ -10,7 +10,7 @@ import { ensurePermission } from '../utils'; /** * Get all system vars. */ -export const getVars = async(context: CoreContext): Promise => { +export const getVars = async (context: CoreContext): Promise => { const { user } = context; // Bail if the user doesn't have permission diff --git a/app/core/modules/get-welcome.ts b/app/core/modules/get-welcome.ts index 3d8c3019b..685336f15 100644 --- a/app/core/modules/get-welcome.ts +++ b/app/core/modules/get-welcome.ts @@ -11,7 +11,7 @@ import { getUnraidVersion } from '.'; * Get welcome message. * @returns Welcomes a user. */ -export const getWelcome = async(context: CoreContext): Promise => { +export const getWelcome = async (context: CoreContext): Promise => { const { user } = context; // Bail if the user doesn't have permission diff --git a/app/core/modules/info/get-all-devices.ts b/app/core/modules/info/get-all-devices.ts index 7ae1a12f5..56abe510d 100644 --- a/app/core/modules/info/get-all-devices.ts +++ b/app/core/modules/info/get-all-devices.ts @@ -88,7 +88,7 @@ const systemPciDevices = async (): Promise => { */ const processedDevices = await filterDevices(filteredDevices).then(devices => { return devices - // @ts-ignore + // @ts-expect-error .map(addDeviceClass) .map(device => { // Attempt to get the current kernel-bound driver for this pci device @@ -140,7 +140,7 @@ const systemAudioDevices = systemPciDevices().then(devices => { * System usb devices. * @returns Array of USB devices. */ -const getSystemUSBDevices = async(): Promise => { +const getSystemUSBDevices = async (): Promise => { // Get a list of all usb hubs so we can filter the allowed/disallowed const usbHubs = await execa('cat /sys/bus/usb/drivers/hub/*/modalias', { shell: true }).then(({ stdout }) => { return stdout.split('\n').map(line => { @@ -153,7 +153,7 @@ const getSystemUSBDevices = async(): Promise => { const filterBootDrive = (device: Readonly): boolean => varState?.data?.flashGuid !== device.guid; // Remove usb hubs - // @ts-ignore + // @ts-expect-error const filterUsbHubs = (device: Readonly): boolean => !usbHubs.includes(device.id); // Clean up the name @@ -165,7 +165,7 @@ const getSystemUSBDevices = async(): Promise => { }; }; - const parseDeviceLine = (line: Readonly): { value: string, string: string } => { + const parseDeviceLine = (line: Readonly): { value: string; string: string } => { const emptyLine = { value: '', string: '' }; // If the line is blank return nothing @@ -231,7 +231,7 @@ const getSystemUSBDevices = async(): Promise => { }) || []; // Get all usb devices - const usbDevices = await execa('lsusb').then(async({ stdout }) => { + const usbDevices = await execa('lsusb').then(async ({ stdout }) => { return parseUsbDevices(stdout) .map(parseDevice) .filter(filterBootDrive) @@ -245,7 +245,7 @@ const getSystemUSBDevices = async(): Promise => { /** * Get device info. */ -export const getAllDevices = async function(context: Readonly): Promise { +export const getAllDevices = async function (context: Readonly): Promise { const { user } = context; // Check permissions diff --git a/app/core/modules/info/get-app-count.ts b/app/core/modules/info/get-app-count.ts index 0c36b517f..afb53bf61 100644 --- a/app/core/modules/info/get-app-count.ts +++ b/app/core/modules/info/get-app-count.ts @@ -24,7 +24,7 @@ interface Result extends CoreResult { /** * Get count of docker containers */ -export const getAppCount = async function(context: Readonly): Promise { +export const getAppCount = async function (context: Readonly): Promise { const { user } = context; // Check permissions diff --git a/app/core/modules/info/get-baseboard.ts b/app/core/modules/info/get-baseboard.ts index b27ce28b7..41e3b89d7 100644 --- a/app/core/modules/info/get-baseboard.ts +++ b/app/core/modules/info/get-baseboard.ts @@ -7,7 +7,7 @@ import si from 'systeminformation'; import { CoreContext, CoreResult } from '../../types'; import { ensurePermission } from '../../utils'; -export const getBaseboard = async(context: CoreContext): Promise => { +export const getBaseboard = async (context: CoreContext): Promise => { const { user } = context; // Check permissions diff --git a/app/core/modules/info/get-cpu.ts b/app/core/modules/info/get-cpu.ts index 42927bea4..ef884a2eb 100644 --- a/app/core/modules/info/get-cpu.ts +++ b/app/core/modules/info/get-cpu.ts @@ -10,7 +10,7 @@ import { ensurePermission } from '../../utils'; /** * Get CPU info. */ -export const getCpu = async function(context: CoreContext): Promise { +export const getCpu = async function (context: CoreContext): Promise { const { user } = context; // Check permissions diff --git a/app/core/modules/info/get-display.ts b/app/core/modules/info/get-display.ts index 5825af3e6..05e30a3e4 100644 --- a/app/core/modules/info/get-display.ts +++ b/app/core/modules/info/get-display.ts @@ -28,7 +28,7 @@ const toBoolean = (prop: string): boolean => prop.toLowerCase() === 'true'; /** * Get display info. */ -export const getDisplay = async function(context: CoreContext): Promise { +export const getDisplay = async function (context: CoreContext): Promise { const { user } = context; // Check permissions diff --git a/app/core/modules/info/get-machine-id.ts b/app/core/modules/info/get-machine-id.ts index e3a00c714..b4c33acb6 100644 --- a/app/core/modules/info/get-machine-id.ts +++ b/app/core/modules/info/get-machine-id.ts @@ -9,7 +9,7 @@ import { ensurePermission, getMachineId as getMachineIdFromFile } from '../../ut /** * Get the machine ID. */ -export const getMachineId = async function(context: CoreContext): Promise { +export const getMachineId = async function (context: CoreContext): Promise { const { user } = context; // Check permissions diff --git a/app/core/modules/info/get-memory.ts b/app/core/modules/info/get-memory.ts index 5077616da..5b8c44c84 100644 --- a/app/core/modules/info/get-memory.ts +++ b/app/core/modules/info/get-memory.ts @@ -13,7 +13,7 @@ import { cleanStdout, ensurePermission } from '../../utils'; /** * Get memory. */ -export const getMemory = async(context: CoreContext): Promise => { +export const getMemory = async (context: CoreContext): Promise => { const { user } = context; // Check permissions diff --git a/app/core/modules/info/get-os.ts b/app/core/modules/info/get-os.ts index 8df2c5121..6e5cb98c8 100644 --- a/app/core/modules/info/get-os.ts +++ b/app/core/modules/info/get-os.ts @@ -13,7 +13,7 @@ import { ensurePermission } from '../../utils'; * @memberof Core * @module info/get-os */ -export const getOs = async function(context: CoreContext): Promise { +export const getOs = async function (context: CoreContext): Promise { const { user } = context; // Check permissions diff --git a/app/core/modules/info/get-software-versions.ts b/app/core/modules/info/get-software-versions.ts index 037fb7f3a..fe4672f56 100644 --- a/app/core/modules/info/get-software-versions.ts +++ b/app/core/modules/info/get-software-versions.ts @@ -14,7 +14,7 @@ const cache = new CacheManager('unraid:modules:get-system-versions'); * Software versions. * @returns Versions of all the core software. */ -export const getSoftwareVersions = async(context: CoreContext): Promise => { +export const getSoftwareVersions = async (context: CoreContext): Promise => { const { user } = context; // Check permissions diff --git a/app/core/modules/info/get-unraid-version.ts b/app/core/modules/info/get-unraid-version.ts index 05acea8d9..fee098c06 100644 --- a/app/core/modules/info/get-unraid-version.ts +++ b/app/core/modules/info/get-unraid-version.ts @@ -15,15 +15,15 @@ const cache = new CacheManager('unraid:modules:get-unraid-version'); interface Result extends CoreResult { json: { - unraid: string - } + unraid: string; + }; } /** * Unraid version string. * @returns The current version. */ -export const getUnraidVersion = async(context: CoreContext): Promise => { +export const getUnraidVersion = async (context: CoreContext): Promise => { const { user } = context; // Check permissions diff --git a/app/core/modules/info/get-versions.ts b/app/core/modules/info/get-versions.ts index 578fafafb..03f83fe2c 100644 --- a/app/core/modules/info/get-versions.ts +++ b/app/core/modules/info/get-versions.ts @@ -9,7 +9,7 @@ import { CoreResult, CoreContext } from '../../types'; /** * Get all version info. */ -export const getVersions = async function(context: CoreContext): Promise { +export const getVersions = async function (context: CoreContext): Promise { const unraidVersion = await getUnraidVersion(context).then(result => result.json); const softwareVersions = await getSoftwareVersions(context).then(result => result.json); diff --git a/app/core/modules/services/get-emhttpd.ts b/app/core/modules/services/get-emhttpd.ts index bc891066e..87047d0da 100644 --- a/app/core/modules/services/get-emhttpd.ts +++ b/app/core/modules/services/get-emhttpd.ts @@ -17,7 +17,7 @@ interface Result extends CoreResult { /** * Get emhttpd service info. */ -export const getEmhttpdService = async(context: CoreContext): Promise => { +export const getEmhttpdService = async (context: CoreContext): Promise => { const { user } = context; // Check permissions diff --git a/app/core/modules/services/get-unraid-api.ts b/app/core/modules/services/get-unraid-api.ts index ea7edb2ed..983a79bc1 100644 --- a/app/core/modules/services/get-unraid-api.ts +++ b/app/core/modules/services/get-unraid-api.ts @@ -9,13 +9,13 @@ import { CoreContext, CoreResult } from '../../types'; const namespace = 'unraid-api'; interface Result extends CoreResult { - json: NodeService + json: NodeService; } /** * Get Unraid api service info. */ -export const getUnraidApiService = async(context: CoreContext): Promise => { +export const getUnraidApiService = async (context: CoreContext): Promise => { const service = await getNodeService(context.user, namespace); return { text: `Service: ${JSON.stringify(service, null, 2)}`, diff --git a/app/core/modules/settings/update-disk.ts b/app/core/modules/settings/update-disk.ts index f09ca75f2..a017985e4 100644 --- a/app/core/modules/settings/update-disk.ts +++ b/app/core/modules/settings/update-disk.ts @@ -26,7 +26,7 @@ interface Result extends CoreResult { /** * Update disk settings. */ -export const updateDisk = async(context: Context): Promise => { +export const updateDisk = async (context: Context): Promise => { const { data, user } = context; // Check permissions @@ -43,7 +43,7 @@ export const updateDisk = async(context: Context): Promise => { * @param allowedValues Which values which are allowed. * @param optional If the value can also be undefined. */ - const check = (property: string, allowedValues: { [key: string]: string } | string[], optional = true): void => { + const check = (property: string, allowedValues: Record | string[], optional = true): void => { const value = data[property]; // Skip checking if the value isn't needed and it's not set diff --git a/app/core/modules/shares/name/get-share.ts b/app/core/modules/shares/name/get-share.ts index fd4aa7816..0408306a2 100644 --- a/app/core/modules/shares/name/get-share.ts +++ b/app/core/modules/shares/name/get-share.ts @@ -21,7 +21,7 @@ interface Result extends CoreResult { /** * Get single share. */ -export const getShare = async function(context: Context): Promise { +export const getShare = async function (context: Context): Promise { const { params, user } = context; const { name } = params; diff --git a/app/core/modules/users/id/add-role.ts b/app/core/modules/users/id/add-role.ts index 768c7c2da..5d59f3d5b 100644 --- a/app/core/modules/users/id/add-role.ts +++ b/app/core/modules/users/id/add-role.ts @@ -18,7 +18,7 @@ interface Context extends CoreContext { /** * Add role to user. */ -export const addRole = async(context: Context): Promise => { +export const addRole = async (context: Context): Promise => { const { user, params } = context; // Check permissions diff --git a/app/core/modules/users/id/delete-user.ts b/app/core/modules/users/id/delete-user.ts index 61cdc3f4f..e2abe1f4f 100644 --- a/app/core/modules/users/id/delete-user.ts +++ b/app/core/modules/users/id/delete-user.ts @@ -18,7 +18,7 @@ interface Context extends CoreContext { /** * Delete user account. */ -export const deleteUser = async(context: Context): Promise => { +export const deleteUser = async (context: Context): Promise => { // Check permissions ensurePermission(context.user, { resource: 'user', diff --git a/app/core/modules/users/id/get-user.ts b/app/core/modules/users/id/get-user.ts index 0d71c8af4..a1e66fd98 100644 --- a/app/core/modules/users/id/get-user.ts +++ b/app/core/modules/users/id/get-user.ts @@ -20,7 +20,7 @@ interface Context extends CoreContext { * Get single user. * @returns The selected user. */ -export const getUser = async(context: Context): Promise => { +export const getUser = async (context: Context): Promise => { // Check permissions ensurePermission(context.user, { resource: 'user', diff --git a/app/core/modules/vms/domains/domain/get-domain.ts b/app/core/modules/vms/domains/domain/get-domain.ts index 908d54f62..dee60aaba 100644 --- a/app/core/modules/vms/domains/domain/get-domain.ts +++ b/app/core/modules/vms/domains/domain/get-domain.ts @@ -17,7 +17,7 @@ interface Context extends CoreContext { /** * Get a single vm domain. */ -export const getDomain = async function(context: Context): Promise { +export const getDomain = async function (context: Context): Promise { const { params, user } = context; const { name } = params; diff --git a/app/core/modules/vms/get-domains.ts b/app/core/modules/vms/get-domains.ts index bfc7860ba..8ca55dfd8 100644 --- a/app/core/modules/vms/get-domains.ts +++ b/app/core/modules/vms/get-domains.ts @@ -9,7 +9,7 @@ import { parseDomains, getHypervisor, ensurePermission } from '../../utils'; /** * Get vm domains. */ -export const getDomains = async(context: CoreContext): Promise => { +export const getDomains = async (context: CoreContext): Promise => { const { user } = context; // Check permissions diff --git a/app/core/notifiers/email.ts b/app/core/notifiers/email.ts index 2ac6285a4..ccdfd1383 100644 --- a/app/core/notifiers/email.ts +++ b/app/core/notifiers/email.ts @@ -8,12 +8,12 @@ import { log } from '../log'; import { Notifier, NotifierOptions, NotifierSendOptions } from './notifier'; interface Options extends NotifierOptions { - to: string - from?: string - replyTo?: string + to: string; + from?: string; + replyTo?: string; } -interface SendOptions extends NotifierSendOptions {}; +interface SendOptions extends NotifierSendOptions {} /** * Email notifer diff --git a/app/core/notifiers/http.ts b/app/core/notifiers/http.ts index efe0331e9..51a5886ab 100644 --- a/app/core/notifiers/http.ts +++ b/app/core/notifiers/http.ts @@ -6,7 +6,7 @@ import fetch from 'node-fetch'; import { Notifier, NotifierOptions } from './notifier'; -export interface Options extends NotifierOptions {}; +export interface Options extends NotifierOptions {} /** * HTTP notifier. diff --git a/app/core/notifiers/notifier.ts b/app/core/notifiers/notifier.ts index a368be3d1..463be7f52 100644 --- a/app/core/notifiers/notifier.ts +++ b/app/core/notifiers/notifier.ts @@ -10,7 +10,7 @@ export type NotifierLevel = 'info' | 'error' | 'debug'; export interface NotifierOptions { level: NotifierLevel; - helpers?: object; + helpers?: Record; template?: string; } diff --git a/app/core/notifiers/unraid.ts b/app/core/notifiers/unraid.ts index 179f1c98b..7913550aa 100644 --- a/app/core/notifiers/unraid.ts +++ b/app/core/notifiers/unraid.ts @@ -9,7 +9,7 @@ import { NotifierSendOptions } from './notifier'; type Transport = 'email' | 'push' | 'ios' | 'android'; interface Options extends HttpNotifierOptions { - transport?: Transport + transport?: Transport; } /** diff --git a/app/core/permission-manager.ts b/app/core/permission-manager.ts index b19bdebc8..0ca3565f4 100644 --- a/app/core/permission-manager.ts +++ b/app/core/permission-manager.ts @@ -3,7 +3,7 @@ * Written by: Alexis Tyler */ -// @ts-ignore +// @ts-expect-error import { validate as validateArgument } from 'bycontract'; import { LooseObject } from './types'; import { AppError } from './errors'; @@ -72,7 +72,7 @@ class PermissionManager { }; // Update the known list of valid scopes - // @ts-ignore + // @ts-expect-error this.knownScopes = [ ...this.knownScopes, ...Object.values(scopeObject) diff --git a/app/core/permissions.ts b/app/core/permissions.ts index d59cb6733..f97bdf1f9 100644 --- a/app/core/permissions.ts +++ b/app/core/permissions.ts @@ -10,14 +10,12 @@ interface Permission { resource: string; action: string; attributes: string; -}; +} -interface Permissions { - [roleName: string]: { - extends?: string | string[]; - permissions?: Permission[]; - }; -}; +type Permissions = Record; const loadPermissionsFile = (filePath: string) => { // Create file if it's missing diff --git a/app/core/plugin-manager.ts b/app/core/plugin-manager.ts index f82225512..ab4e0da89 100644 --- a/app/core/plugin-manager.ts +++ b/app/core/plugin-manager.ts @@ -188,7 +188,7 @@ export class PluginManager { } // Initialize plugin - await Promise.resolve(plugin.init(context, core)).then(async() => { + await Promise.resolve(plugin.init(context, core)).then(async () => { coreLogger.debug('Plugin "%s" loaded successfully.', pluginName); // Add to manager diff --git a/app/core/states/network.ts b/app/core/states/network.ts index b45c7048e..afc04e4b4 100644 --- a/app/core/states/network.ts +++ b/app/core/states/network.ts @@ -11,39 +11,37 @@ import { toBoolean } from '../utils/casting'; import { parseConfig } from '../utils/misc'; import { ArrayState } from './state'; -interface NetworkIni { - [key: string]: { - dhcpKeepresolv: IniStringBoolean; - dnsServer1: string; - dnsServer2: string; - dhcp6Keepresolv: IniStringBoolean; - bonding: IniStringBoolean; - bondname: string; - bondnics: CommaSeparatedString; - bondingMode: string; - bondingMiimon: string; - bridging: IniStringBoolean; - brname: string; - brnics: string; - brstp: string; - brfd: string; - 'description:0': string; - 'protocol:0': string; - 'useDhcp:0': IniStringBoolean; - 'ipaddr:0': string; - 'netmask:0': string; - 'gateway:0': string; - 'metric:0': string; - 'useDhcp6:0': IniStringBoolean; - 'ipaddr6:0': string; - 'netmask6:0': string; - 'gateway6:0': string; - 'metric6:0': string; - 'privacy6:0': string; - mtu: string; - type: string; - }; -} +type NetworkIni = Record; const parse = (state: NetworkIni) => { return Object.values(state).map(network => { diff --git a/app/core/states/smb-sec.ts b/app/core/states/smb-sec.ts index 621ecc245..427326d77 100644 --- a/app/core/states/smb-sec.ts +++ b/app/core/states/smb-sec.ts @@ -92,7 +92,7 @@ class SmbSec extends ArrayState { return data; } - find(query?: {}): any[] { + find(query?: Record): any[] { return super.find(query); } } diff --git a/app/core/states/state.ts b/app/core/states/state.ts index 965e480f7..5bc35f7c5 100644 --- a/app/core/states/state.ts +++ b/app/core/states/state.ts @@ -11,9 +11,8 @@ type Mutation = 'CREATED' | 'UPDATED' | 'DELETED'; export class State { channel?: string; - _data?: { - [key: string]: any; - }; + _data?: Record; + lastUpdated: Date; constructor() { @@ -42,7 +41,9 @@ export class State { const channel = this.channel; // Bail since we have no channel to post to - if (!channel) return; + if (!channel) { + return; + } // Update channel with new state bus.emit(channel, { diff --git a/app/core/types/global.ts b/app/core/types/global.ts index da72d1093..4f3bfe64d 100644 --- a/app/core/types/global.ts +++ b/app/core/types/global.ts @@ -5,13 +5,9 @@ import { User } from './states'; */ export type CommaSeparatedString = string; -export interface LooseObject { - [key: string]: any; -} +export type LooseObject = Record; -export interface LooseStringObject { - [key: string]: string; -} +export type LooseStringObject = Record; /** * Context object @@ -20,9 +16,9 @@ export interface LooseStringObject { * @property param Params object. */ export interface CoreContext { - readonly query?: Readonly<{ [key: string]: any }>; - readonly data?: Readonly<{ [key: string]: any }>; - readonly params?: Readonly<{ [key: string]: string }>; + readonly query?: Readonly>; + readonly data?: Readonly>; + readonly params?: Readonly>; readonly user: Readonly; } @@ -30,7 +26,7 @@ export interface CoreContext { * Result object */ export interface CoreResult { - json?: {}; + json?: Record; text?: string; html?: string; } diff --git a/app/core/types/states/var.ts b/app/core/types/states/var.ts index f43cd1d2a..267b710cf 100644 --- a/app/core/types/states/var.ts +++ b/app/core/types/states/var.ts @@ -163,7 +163,7 @@ export interface Var { /** Which mode is smb running in? active-directory | workgroup */ shareSmbMode: string; shareUser: string; - // shareUserExclude + // ShareUserExclude shutdownTimeout: number; /** How long until emhttpd should spin down the data drives in your array. */ spindownDelay: number; diff --git a/app/core/utils/authentication/check-auth.ts b/app/core/utils/authentication/check-auth.ts index 87218fad2..5b83d744d 100644 --- a/app/core/utils/authentication/check-auth.ts +++ b/app/core/utils/authentication/check-auth.ts @@ -15,7 +15,7 @@ interface Options { /** * Check if the username and password match a htpasswd file. */ -export const checkAuth = async(options: Options): Promise => { +export const checkAuth = async (options: Options): Promise => { const { username, password, file } = options; // `valid` will be true if and only if diff --git a/app/core/utils/authentication/ensure-auth.ts b/app/core/utils/authentication/ensure-auth.ts index 72d6df42c..05243b8dd 100644 --- a/app/core/utils/authentication/ensure-auth.ts +++ b/app/core/utils/authentication/ensure-auth.ts @@ -16,7 +16,7 @@ interface Options { /** * Check if the username and password match a htpasswd file */ -export const ensureAuth = async(options: Options) => { +export const ensureAuth = async (options: Options) => { const { username, password, file } = options; // `valid` will be true if and only if diff --git a/app/core/utils/clients/emcmd.ts b/app/core/utils/clients/emcmd.ts index cb9d4ded4..0b3c259ff 100644 --- a/app/core/utils/clients/emcmd.ts +++ b/app/core/utils/clients/emcmd.ts @@ -17,7 +17,7 @@ const dryRun = envs.DRY_RUN; /** * Run a command with emcmd. */ -export const emcmd = async(commands: LooseObject) => { +export const emcmd = async (commands: LooseObject) => { const url = `http://unix:${socketPath}:/update.htm`; const options = { qs: { diff --git a/app/core/utils/clients/nchan.ts b/app/core/utils/clients/nchan.ts index b50a4efdc..ce48c7c3a 100644 --- a/app/core/utils/clients/nchan.ts +++ b/app/core/utils/clients/nchan.ts @@ -18,7 +18,7 @@ const getSubEndpoint = () => { return `http://localhost:${httpPort}/sub`; }; -export const isNchanUp = async() => { +export const isNchanUp = async () => { const isUp = await fetch(`${getSubEndpoint()}/non-existant`, { method: 'HEAD' }) @@ -50,10 +50,10 @@ const endpointToStateMapping = { var: states.varState }; -const subscribe = async(endpoint: string) => { - await sleep(1000).then(async() => { +const subscribe = async (endpoint: string) => { + await sleep(1000).then(async () => { debugTimer(`subscribe(${endpoint})`); - const response = await fetch(`${getSubEndpoint()}/${endpoint}`).catch(async() => { + const response = await fetch(`${getSubEndpoint()}/${endpoint}`).catch(async () => { // If we throw then let's check if nchan is down // or if it's an actual error const isUp = await isNchanUp(); @@ -109,7 +109,7 @@ const subscribe = async(endpoint: string) => { }); }; -export const subscribeToNchanEndpoint = async(endpoint: string) => { +export const subscribeToNchanEndpoint = async (endpoint: string) => { if (!Object.keys(endpointToStateMapping).includes(endpoint)) { throw new AppError(`Invalid nchan endpoint "${endpoint}".`); } diff --git a/app/core/utils/misc/atomic-sleep.ts b/app/core/utils/misc/atomic-sleep.ts index b1cdd1a62..09a7e67b7 100644 --- a/app/core/utils/misc/atomic-sleep.ts +++ b/app/core/utils/misc/atomic-sleep.ts @@ -7,7 +7,7 @@ * Atomically sleep for a certain amount of milliseconds. * @param ms How many milliseconds to sleep for. */ -export const atomicSleep = (ms: number): Promise => { +export const atomicSleep = async (ms: number): Promise => { return new Promise(resolve => { // eslint-disable-next-line no-undef Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); diff --git a/app/core/utils/misc/catch-handlers.ts b/app/core/utils/misc/catch-handlers.ts index 6dfbccb3e..578c42d11 100644 --- a/app/core/utils/misc/catch-handlers.ts +++ b/app/core/utils/misc/catch-handlers.ts @@ -7,7 +7,7 @@ import { paths } from '../../paths'; import { AppError } from '../../errors'; interface DockerError extends NodeJS.ErrnoException { - address: string + address: string; } /** diff --git a/app/core/utils/misc/get-endpoints.ts b/app/core/utils/misc/get-endpoints.ts index 7b993367f..72f4b25ba 100644 --- a/app/core/utils/misc/get-endpoints.ts +++ b/app/core/utils/misc/get-endpoints.ts @@ -17,16 +17,16 @@ export const getEndpoints = (app: Express) => { return deDeupedEndpoints.map(endpoint => { const flattenedMethods: string[] = flatten([ - // @ts-ignore + // @ts-expect-error endpoint.methods, - // @ts-ignore + // @ts-expect-error endpoints.filter(({ path }) => path === endpoint.path).map(({ methods }) => methods) ]); const methods = flattenedMethods.filter((item, pos, self) => self.indexOf(item) === pos); return { - // @ts-ignore + // @ts-expect-error ...endpoint, methods }; diff --git a/app/core/utils/misc/get-machine-id.ts b/app/core/utils/misc/get-machine-id.ts index b638c00a7..4d0b88973 100644 --- a/app/core/utils/misc/get-machine-id.ts +++ b/app/core/utils/misc/get-machine-id.ts @@ -5,7 +5,7 @@ import { FileMissingError } from '../../errors'; const cache = new CacheManager('unraid:utils:misc/get-machine-id'); -export const getMachineId = async() => { +export const getMachineId = async () => { const path = paths.get('machine-id'); let machineId = cache.get('machine-id'); diff --git a/app/core/utils/misc/get-node-service.ts b/app/core/utils/misc/get-node-service.ts index 67d388d52..23e84539b 100644 --- a/app/core/utils/misc/get-node-service.ts +++ b/app/core/utils/misc/get-node-service.ts @@ -1,159 +1,159 @@ import fs from 'fs'; import path from 'path'; import execa from 'execa'; -import { JSONSchemaForNPMPackageJsonFiles as PackageJson} from '@schemastore/package'; +import { JSONSchemaForNPMPackageJsonFiles as PackageJson } from '@schemastore/package'; import { coreLogger } from '../../log'; import { User } from '../../types'; import { CacheManager } from '../../cache-manager'; import { cleanStdout, ensurePermission } from '../../utils'; export interface NodeService { - online?: boolean; - uptime?: number; - version?: string; + online?: boolean; + uptime?: number; + version?: string; } export const getNodeService = async (user: User, namespace: string): Promise => { - // Check permissions - ensurePermission(user, { - resource: `service/${namespace}`, - action: 'read', - possession: 'any' - }); + // Check permissions + ensurePermission(user, { + resource: `service/${namespace}`, + action: 'read', + possession: 'any' + }); - const cache = new CacheManager(`unraid:services/${namespace}`, Infinity); + const cache = new CacheManager(`unraid:services/${namespace}`, Infinity); - const getCachedValue = (name: string) => { - return cache.get(name); - }; + const getCachedValue = (name: string) => { + return cache.get(name); + }; - const setCachedValue = (name: string, value: string) => { - cache.set(name, value); - return value; - }; + const setCachedValue = (name: string, value: string) => { + cache.set(name, value); + return value; + }; - const getUptime = async (pid: string | number) => { - const uptime = await execa('ps', ['-p', String(pid), '-o', 'etimes', '--no-headers']) - .then(cleanStdout) - .catch(async error => { - // No clue why this failed - if (!error.stderr.includes('keyword not found')) { - return ''; - } + const getUptime = async (pid: string | number) => { + const uptime = await execa('ps', ['-p', String(pid), '-o', 'etimes', '--no-headers']) + .then(cleanStdout) + .catch(async error => { + // No clue why this failed + if (!error.stderr.includes('keyword not found')) { + return ''; + } - // Retry with macos way - // Borrowed from https://stackoverflow.com/a/28856613 - const command = `ps -p ${pid} -oetime= | tr '-' ':' | awk -F: '{ total=0; m=1; } { for (i=0; i < NF; i++) {total += $(NF-i)*m; m *= i >= 2 ? 24 : 60 }} {print total}'`; - return execa.command(command, { shell: true }).then(cleanStdout).catch(() => ''); - }); + // Retry with macos way + // Borrowed from https://stackoverflow.com/a/28856613 + const command = `ps -p ${pid} -oetime= | tr '-' ':' | awk -F: '{ total=0; m=1; } { for (i=0; i < NF; i++) {total += $(NF-i)*m; m *= i >= 2 ? 24 : 60 }} {print total}'`; + return execa.command(command, { shell: true }).then(cleanStdout).catch(() => ''); + }); - const parsedUptime = Number.parseInt(uptime, 10); - return parsedUptime >= 0 ? parsedUptime : -1; - }; + const parsedUptime = Number.parseInt(uptime, 10); + return parsedUptime >= 0 ? parsedUptime : -1; + }; - const getPid = async (): Promise => { - let pid = getCachedValue('pid'); + const getPid = async (): Promise => { + let pid = getCachedValue('pid'); - // Return cached pid - if (pid) { - return pid; - } + // Return cached pid + if (pid) { + return pid; + } - coreLogger.debug('No PID found in cache for %s', namespace); - pid = await execa.command(`pidof ${namespace}`) - .then(output => { - const pids = cleanStdout(output).split('\n'); - return pids[0]; - }) - .catch(async error => { - // `pidof` is missing - if (error.code === 'ENOENT') { - // Fall back to using ps - // ps axc returns a line like the following - // 31424 s005 S+ 0:01.66 node - // We match the last column and return the first - return execa.command('ps axc').then(({stdout}) => { - const foundProcess = stdout.split(/\n/).find(line => { - const field = line.trim().split(/\s+/)[4]; - return field === namespace; - })?.trim(); - return foundProcess ? foundProcess.split(/\s/)[0] : ''; - }); - } + coreLogger.debug('No PID found in cache for %s', namespace); + pid = await execa.command(`pidof ${namespace}`) + .then(output => { + const pids = cleanStdout(output).split('\n'); + return pids[0]; + }) + .catch(async error => { + // `pidof` is missing + if (error.code === 'ENOENT') { + // Fall back to using ps + // ps axc returns a line like the following + // 31424 s005 S+ 0:01.66 node + // We match the last column and return the first + return execa.command('ps axc').then(({ stdout }) => { + const foundProcess = stdout.split(/\n/).find(line => { + const field = line.trim().split(/\s+/)[4]; + return field === namespace; + })?.trim(); + return foundProcess ? foundProcess.split(/\s/)[0] : ''; + }); + } - return ''; - }); + return ''; + }); - // Process is offline - if (!pid) { - return; - } + // Process is offline + if (!pid) { + return; + } - coreLogger.debug('Setting pid for %s to %s', namespace, pid); + coreLogger.debug('Setting pid for %s to %s', namespace, pid); - // Update cache - setCachedValue('pid', pid); + // Update cache + setCachedValue('pid', pid); - // Return new pid - return pid; - }; + // Return new pid + return pid; + }; - const getPkgFilePath = async (pid: number | string) => { - coreLogger.debug('Getting package.json path for %s', namespace); + const getPkgFilePath = async (pid: number | string) => { + coreLogger.debug('Getting package.json path for %s', namespace); - return execa.command(`pwdx ${pid}`).then(({ stdout }) => { - return path.resolve(stdout.split(/\n/)[0].split(':')[1].trim()); - }).catch(async error =>{ - if (error.code === 'ENOENT') { - return execa.command(`lsof -a -d cwd -p ${pid} -n -Fn`).then(({ stdout }) => stdout.split(/\n/)[2].substr(1)); - } + return execa.command(`pwdx ${pid}`).then(({ stdout }) => { + return path.resolve(stdout.split(/\n/)[0].split(':')[1].trim()); + }).catch(async error => { + if (error.code === 'ENOENT') { + return execa.command(`lsof -a -d cwd -p ${pid} -n -Fn`).then(({ stdout }) => stdout.split(/\n/)[2].substr(1)); + } - return ''; - }); - }; + return ''; + }); + }; - const getVersion = async (pid: number | string) => { - let cachedVersion = getCachedValue('version'); + const getVersion = async (pid: number | string) => { + let cachedVersion = getCachedValue('version'); - // Return cached version - if (cachedVersion) { - return cachedVersion; - } + // Return cached version + if (cachedVersion) { + return cachedVersion; + } - // Update local vars - const pkgFilePath = await getPkgFilePath(pid); - const version = await fs.promises.readFile(`${pkgFilePath}/package.json`) - .then(buffer => buffer.toString()) - .then(JSON.parse) - .then((pkg: PackageJson) => pkg.version); + // Update local vars + const pkgFilePath = await getPkgFilePath(pid); + const version = await fs.promises.readFile(`${pkgFilePath}/package.json`) + .then(buffer => buffer.toString()) + .then(JSON.parse) + .then((pkg: PackageJson) => pkg.version); - if (!version) { - return; - } + if (!version) { + return; + } - // Update cache - setCachedValue('version', version); - return version; - }; + // Update cache + setCachedValue('version', version); + return version; + }; - const pid = await getPid(); + const pid = await getPid(); - // If the pid doesn't exist it's offline - if (!pid) { - return { - online: false - }; - } + // If the pid doesn't exist it's offline + if (!pid) { + return { + online: false + }; + } - const version = await getVersion(pid); - const uptime = await getUptime(pid); - const online = uptime >= 1; + const version = await getVersion(pid); + const uptime = await getUptime(pid); + const online = uptime >= 1; - coreLogger.silly('%s version=%s uptime=%s online=%s', namespace, version, uptime, online); + coreLogger.silly('%s version=%s uptime=%s online=%s', namespace, version, uptime, online); - return { - online, - uptime, - version - }; + return { + online, + uptime, + version + }; }; diff --git a/app/core/utils/misc/load-state.ts b/app/core/utils/misc/load-state.ts index 455b1579a..df5c7f961 100644 --- a/app/core/utils/misc/load-state.ts +++ b/app/core/utils/misc/load-state.ts @@ -16,7 +16,7 @@ export const loadState = (filePath: string): T => { type: 'ini' }); - // @ts-ignore + // @ts-expect-error return camelCaseKeys(config, { deep: true }); diff --git a/app/core/utils/misc/parse-config.ts b/app/core/utils/misc/parse-config.ts index 08182b629..34d5fc621 100644 --- a/app/core/utils/misc/parse-config.ts +++ b/app/core/utils/misc/parse-config.ts @@ -32,7 +32,7 @@ const getPath = (filePath?: string) => { } try { - // @ts-ignore + // @ts-expect-error return paths.get(filePath) ?? filePath; } catch { return filePath; @@ -54,15 +54,13 @@ const getPath = (filePath?: string) => { * } * ``` */ -const fixObjectArrays = (object: { - [key: string]: any; -}) => { +const fixObjectArrays = (object: Record) => { // An object of arrays for keys that end in `:${number}` const temporaryArrays = {}; // An object without any array items const filteredObject = filterObject(object, (key, value) => { - const [_, name, index] = [...((key as string).match(/(.*):(\d+$)/) ?? [])]; + const [_, name, index] = [...((key).match(/(.*):(\d+$)/) ?? [])]; if (!name || !index) { return true; } @@ -100,7 +98,7 @@ export const parseConfig = (options: Options): T => { } // Parse file - let data: { [key: string]: any }; + let data: Record; if (filePath) { data = multiIniRead(filePath, { // eslint-disable-next-line camelcase @@ -131,7 +129,7 @@ export const parseConfig = (options: Options): T => { ); // Convert all keys to camel case - // @ts-ignore + // @ts-expect-error return camelCaseKeys(result, { deep: true }); diff --git a/app/core/utils/misc/sleep.ts b/app/core/utils/misc/sleep.ts index 281820e00..b4025fb0f 100644 --- a/app/core/utils/misc/sleep.ts +++ b/app/core/utils/misc/sleep.ts @@ -7,4 +7,4 @@ * Sleep for a certain amount of milliseconds. * @param ms How many milliseconds to sleep for. */ -export const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); +export const sleep = async (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); diff --git a/app/core/utils/misc/validate-api-key-format.ts b/app/core/utils/misc/validate-api-key-format.ts index 40a430fc3..fe1560450 100644 --- a/app/core/utils/misc/validate-api-key-format.ts +++ b/app/core/utils/misc/validate-api-key-format.ts @@ -1,8 +1,8 @@ import { ApiKeyError } from '../../errors'; export const validateApiKeyFormat = (apiKey: string) => { - const key = `${apiKey}`.trim(); - const length = key.length; + const key = `${apiKey}`.trim(); + const length = key.length; // Bail if key is missing if (length === 0) { @@ -25,5 +25,5 @@ export const validateApiKeyFormat = (apiKey: string) => { // For example "XXXXXXXXXXXXXXXXXXX" if (/^(.)\1+$/.test(key)) { throw new ApiKeyError(`Key is same char repeated, ${key}`); - } -} + } +}; diff --git a/app/core/utils/misc/validate-api-key.ts b/app/core/utils/misc/validate-api-key.ts index 3a27996f5..a841bf10a 100644 --- a/app/core/utils/misc/validate-api-key.ts +++ b/app/core/utils/misc/validate-api-key.ts @@ -4,9 +4,9 @@ import { varState } from '../../states'; import { AppError } from '../../errors'; export const validateApiKey = async (apiKey: string) => { - const KEY_SERVER_KEY_VERIFICATION_ENDPOINT = process.env.KEY_SERVER_KEY_VERIFICATION_ENDPOINT ?? `https://keys.lime-technology.com/validate/apikey`; + const KEY_SERVER_KEY_VERIFICATION_ENDPOINT = process.env.KEY_SERVER_KEY_VERIFICATION_ENDPOINT ?? 'https://keys.lime-technology.com/validate/apikey'; - const sendFormToKeyServer = async (url: string, data: {}) => { + const sendFormToKeyServer = async (url: string, data: Record) => { if (!data) { throw new AppError('Missing data field.'); } @@ -37,5 +37,5 @@ export const validateApiKey = async (apiKey: string) => { return false; } - return response.json().then(data => data.valid); + return response.json().then(data => data.valid); }; diff --git a/app/core/utils/permissions/get-permissions.ts b/app/core/utils/permissions/get-permissions.ts index dcf7bd9f3..eb58d4fe0 100644 --- a/app/core/utils/permissions/get-permissions.ts +++ b/app/core/utils/permissions/get-permissions.ts @@ -9,8 +9,8 @@ import { ac } from '../../permissions'; * Get permissions from an {@link https://onury.io/accesscontrol/?api=ac#AccessControl | AccessControl} role. * @param role The {@link https://onury.io/accesscontrol/?api=ac#AccessControl | AccessControl} role to be looked up. */ -export const getPermissions = (role: string): object => { - const grants: {} = ac.getGrants(); +export const getPermissions = (role: string): Record => { + const grants: Record = ac.getGrants(); const { $extend, ...roles } = grants[role]; const inheritedRoles = Array.isArray($extend) ? $extend.map(role => getPermissions(role))[0] : {}; return Object.assign({}, roles, inheritedRoles); diff --git a/app/core/utils/plugins/php-loader.ts b/app/core/utils/plugins/php-loader.ts index 9f679b29c..139c163d6 100644 --- a/app/core/utils/plugins/php-loader.ts +++ b/app/core/utils/plugins/php-loader.ts @@ -37,7 +37,7 @@ interface Options { /** * Load a PHP file. */ -export const phpLoader = async(options: Options) => { +export const phpLoader = async (options: Options) => { const { file, method = 'GET', query = {}, body = {} } = options; const options_ = [ './wrapper.php', diff --git a/app/core/utils/upcast.ts b/app/core/utils/upcast.ts index 8f265dfd5..9aa44ff87 100644 --- a/app/core/utils/upcast.ts +++ b/app/core/utils/upcast.ts @@ -17,7 +17,7 @@ const guardTypeArg = (type: string) => { * * @param type handler's type */ -const guardTypeHandler = (type: () => {}) => { +const guardTypeHandler = (type: () => Record) => { if (typeof type !== 'function') { throw new TypeError('Invalid argument: handler is expected to be a function'); } @@ -29,9 +29,8 @@ const guardTypeHandler = (type: () => {}) => { * @class Upcast */ class Upcast { - alias: { - [key: string]: string - }; + alias: Record; + cast: any; /** @@ -62,7 +61,7 @@ class Upcast { // Default casters this.cast = { - array: (value: T) => [value], + array: (value: T) => [value], boolean: (value: unknown) => Boolean(value), function: (value: unknown) => () => value, null: () => null, @@ -118,7 +117,7 @@ class Upcast { /** * Add custom cast */ - add(type: string, handler: () => {}) { + add(type: string, handler: () => Record) { guardTypeArg(type); guardTypeHandler(handler); diff --git a/app/core/utils/vms/filter-devices.ts b/app/core/utils/vms/filter-devices.ts index bbc2fe480..ddd78a25f 100644 --- a/app/core/utils/vms/filter-devices.ts +++ b/app/core/utils/vms/filter-devices.ts @@ -18,8 +18,8 @@ interface Device { * @param devices Devices to be checked. * @returns Processed devices. */ -export const filterDevices = (devices: Device[]): Promise => { - return asyncMap(devices, async(device: Device) => { +export const filterDevices = async (devices: Device[]): Promise => { + return asyncMap(devices, async (device: Device) => { // Don't run if we don't have the udevadm command available if (!commandExistsSync('udevadm')) { return device; diff --git a/app/core/utils/vms/get-hypervisor.ts b/app/core/utils/vms/get-hypervisor.ts index aa9681042..c19a17236 100644 --- a/app/core/utils/vms/get-hypervisor.ts +++ b/app/core/utils/vms/get-hypervisor.ts @@ -10,7 +10,7 @@ import { AppError } from '../../errors'; let libvirt; let client; -export const getHypervisor = async() => { +export const getHypervisor = async () => { // Return client if it's already connected if (client) { return client; diff --git a/app/core/utils/vms/get-pci-devices.ts b/app/core/utils/vms/get-pci-devices.ts index bdf1e81ed..0edda7608 100644 --- a/app/core/utils/vms/get-pci-devices.ts +++ b/app/core/utils/vms/get-pci-devices.ts @@ -14,7 +14,7 @@ const regex = new RegExp(/^(?\S+) "(?[^"]+) \[(?[a-f\d]{4})]" * * @returns Array of PCI devices */ -export const getPciDevices = async(): Promise => { +export const getPciDevices = async (): Promise => { const devices = await execa('lspci', ['-m', '-nn']) .catch(() => ({ stdout: '' })) .then(cleanStdout); diff --git a/app/core/utils/vms/parse-domain.ts b/app/core/utils/vms/parse-domain.ts index e0168334d..a3b836d23 100644 --- a/app/core/utils/vms/parse-domain.ts +++ b/app/core/utils/vms/parse-domain.ts @@ -16,7 +16,7 @@ export type DomainLookupType = 'id' | 'uuid' | 'name'; * @param id The domain's ID, UUID or name. * @private */ -export const parseDomain = async(type: DomainLookupType, id: string): Promise => { +export const parseDomain = async (type: DomainLookupType, id: string): Promise => { const types = { id: 'lookupDomainByIdAsync', uuid: 'lookupDomainByUUIDAsync', @@ -50,6 +50,6 @@ export const parseDomain = async(type: DomainLookupType, id: string): Promise => { - return Promise.all(domains.map(domain => parseDomain(type, domain))); +export const parseDomains = async (type: DomainLookupType, domains: string[]): Promise => { + return Promise.all(domains.map(async domain => parseDomain(type, domain))); }; diff --git a/app/core/utils/write-to-boot.ts b/app/core/utils/write-to-boot.ts index 8128ac09e..d0496aeb1 100644 --- a/app/core/utils/write-to-boot.ts +++ b/app/core/utils/write-to-boot.ts @@ -4,12 +4,12 @@ import prettyBytes from 'pretty-bytes'; import { coreLogger } from '../log'; const writeFile = (filePath: string, fileContents: string | Buffer) => { - coreLogger.debug(`Writing ${prettyBytes(fileContents.length)} to ${filePath}`); - fs.promises.writeFile(filePath, fileContents); -} + coreLogger.debug(`Writing ${prettyBytes(fileContents.length)} to ${filePath}`); + fs.promises.writeFile(filePath, fileContents); +}; export const writeToBoot = (filePath: string, fileContents: string | Buffer) => { - const basePath = `/boot/config/plugins/dynamix/`; - const resolvedPath = path.resolve(basePath, filePath); - return writeFile(resolvedPath, fileContents); + const basePath = '/boot/config/plugins/dynamix/'; + const resolvedPath = path.resolve(basePath, filePath); + writeFile(resolvedPath, fileContents); }; diff --git a/app/core/watchers/plugins.ts b/app/core/watchers/plugins.ts index ae151bb65..d06bafddd 100644 --- a/app/core/watchers/plugins.ts +++ b/app/core/watchers/plugins.ts @@ -15,7 +15,7 @@ export const plugins = () => { let timeout: NodeJS.Timeout; const reloadPlugins = () => { - coreLogger.debug(`Reloading plugins as it's been %s since last event.`, prettyMilliseconds(PLUGIN_RELOAD_TIME_MS)); + coreLogger.debug('Reloading plugins as it\'s been %s since last event.', prettyMilliseconds(PLUGIN_RELOAD_TIME_MS)); // Reload plugins // core.loaders.plugins(); @@ -50,7 +50,7 @@ export const plugins = () => { watchers.push(watcher); }, stop() { - watchers.forEach(watcher => watcher.close()); + watchers.forEach(async watcher => watcher.close()); } }; }; diff --git a/app/core/watchers/states.ts b/app/core/watchers/states.ts index 374f5fb0e..548f90048 100644 --- a/app/core/watchers/states.ts +++ b/app/core/watchers/states.ts @@ -10,19 +10,19 @@ import { coreLogger } from '../log'; import { devicesState, networkState, nfsSecState, sharesState, slotsState, smbSecState, usersState, varState, ArrayState, State } from '../states'; const stateMapping = { - 'devs.ini': devicesState, - 'network.ini': networkState, - 'sec_nfs.ini': nfsSecState, - 'shares.ini': sharesState, - 'disks.ini': slotsState, - 'sec.ini': smbSecState, - 'users.ini': usersState, - 'var.ini': varState + 'devs.ini': devicesState, + 'network.ini': networkState, + 'sec_nfs.ini': nfsSecState, + 'shares.ini': sharesState, + 'disks.ini': slotsState, + 'sec.ini': smbSecState, + 'users.ini': usersState, + 'var.ini': varState }; const getState = (fullPath: string) => { - const fileName = fullPath.split('/').pop()!; - return Object.keys(stateMapping).includes(fileName) ? stateMapping[fileName] : undefined; + const fileName = fullPath.split('/').pop()!; + return Object.keys(stateMapping).includes(fileName) ? stateMapping[fileName] : undefined; }; export const states = () => { @@ -32,22 +32,22 @@ export const states = () => { let timeout: NodeJS.Timeout; const reloadState = (state: ArrayState | State) => () => { - coreLogger.debug(`Reloading state as it's been %s since last event.`, prettyMilliseconds(STATES_RELOAD_TIME_MS)); + coreLogger.debug('Reloading state as it\'s been %s since last event.', prettyMilliseconds(STATES_RELOAD_TIME_MS)); - // Reload state - try { - state.reset(); - } catch (error) { - coreLogger.error('failed resetting state', error); - } + // Reload state + try { + state.reset(); + } catch (error) { + coreLogger.error('failed resetting state', error); + } }; return { start() { - // Only run if NCHAN is disabled - if (process.env.NCHAN !== 'disable') { - return; - } + // Only run if NCHAN is disabled + if (process.env.NCHAN !== 'disable') { + return; + } // Update states when state files change const watcher = chokidar.watch(statesCwd, { @@ -56,20 +56,20 @@ export const states = () => { ignored: (path: string) => ['node_modules'].some(s => path.includes(s)) }); - coreLogger.debug('Loading watchers for %s', statesCwd); + coreLogger.debug('Loading watchers for %s', statesCwd); // State file has updated, updating state object watcher.on('all', (event, fullPath) => { // Reset timeout - clearTimeout(timeout); + clearTimeout(timeout); - // Ensure we only handle known files - const state = getState(fullPath); - if (!state) { - return; - } + // Ensure we only handle known files + const state = getState(fullPath); + if (!state) { + return; + } - // Update timeout + // Update timeout timeout = setTimeout(reloadState(state), STATES_RELOAD_TIME_MS); coreLogger.debug('States directory %s has emitted %s event.', fullPath, event); }); @@ -78,7 +78,7 @@ export const states = () => { watchers.push(watcher); }, stop() { - watchers.forEach(watcher => watcher.close()); + watchers.forEach(async watcher => watcher.close()); } }; }; diff --git a/app/graphql/index.ts b/app/graphql/index.ts index bd9c3434f..be966af84 100644 --- a/app/graphql/index.ts +++ b/app/graphql/index.ts @@ -161,12 +161,12 @@ const getPluginModule = (pluginName: string, pluginModuleName: string) => { * including the field name, path to the field from the root, and more. */ class FuncDirective extends SchemaDirectiveVisitor { - visitFieldDefinition(field: { [key: string]: any }) { - // @ts-ignore + visitFieldDefinition(field: Record) { + // @ts-expect-error const { args } = this; - field.resolve = async function (_source, directiveArgs: { [key: string]: any }, { user }, info: { [key: string]: any }) { - const {module: moduleName, result: resultType} = args; - const {plugin: pluginName, module: pluginModuleName, result: pluginType, input, ...params} = directiveArgs; + field.resolve = async function (_source, directiveArgs: Record, { user }, info: Record) { + const { module: moduleName, result: resultType } = args; + const { plugin: pluginName, module: pluginModuleName, result: pluginType, input, ...params } = directiveArgs; const operationType = info.operation.operation; const query = { ...directiveArgs.query, @@ -189,7 +189,7 @@ class FuncDirective extends SchemaDirectiveVisitor { let func; try { if (pluginName) { - // @ts-ignore + // @ts-expect-error const { filePath } = getPluginModule(pluginName, pluginModuleName); const pluginModule = require(filePath); // The file will either use a default export or a named one @@ -266,10 +266,8 @@ const ensureApiKey = (apiKeyToCheck: string) => { if (!apiManager.isValid(apiKeyToCheck)) { throw new AppError('Invalid API key.'); } - } else { - if (process.env.NODE_ENV !== 'development') { - throw new AppError('No valid API keys active.'); - } + } else if (process.env.NODE_ENV !== 'development') { + throw new AppError('No valid API keys active.'); } }; @@ -309,7 +307,7 @@ bus.on('slots', async () => { }); // Update info/hostname when hostname changes -bus.on('varstate', async (data) => { +bus.on('varstate', async data => { const hostname = data.varstate.node.name; // @todo: Create a system user for this const user = usersState.findOne({ name: 'root' }); @@ -370,13 +368,13 @@ setIntervalAsync(async () => { export const graphql = { introspection: debug, playground: debug ? { - subscriptionEndpoint: '/graphql', + subscriptionEndpoint: '/graphql' } : false, schema, types, resolvers, subscriptions: { - onConnect: async (connectionParams: { [key: string]: string }) => new Promise((resolve, reject) => { + onConnect: async (connectionParams: Record) => new Promise((resolve, reject) => { try { const apiKey = connectionParams['x-api-key']; const user = apiKeyToUser(apiKey); @@ -387,10 +385,10 @@ export const graphql = { // Update ws connection count and other needed values wsHasConnected(websocketId); - return resolve({ + resolve({ user, websocketId - }); + }); return; } catch (error) { reject(error); } diff --git a/app/graphql/resolvers/index.ts b/app/graphql/resolvers/index.ts index f81607a9c..d3db0fe91 100644 --- a/app/graphql/resolvers/index.ts +++ b/app/graphql/resolvers/index.ts @@ -15,7 +15,7 @@ export const Long = GraphQLLong; export const UUID = GraphQLUUID; export { - Query, - Subscription, - UserAccount -}; \ No newline at end of file + Query, + Subscription, + UserAccount +}; diff --git a/app/graphql/resolvers/query/display.ts b/app/graphql/resolvers/query/display.ts index 475a44fa1..0322ee84f 100644 --- a/app/graphql/resolvers/query/display.ts +++ b/app/graphql/resolvers/query/display.ts @@ -73,77 +73,77 @@ const states = { }; export default async () => { - const dynamixBasePath = paths.get('dynamix-base')!; - const configFilePath = join(dynamixBasePath, 'case-model.cfg'); - const customImageFilePath = join(dynamixBasePath, 'case-model.png'); + const dynamixBasePath = paths.get('dynamix-base')!; + const configFilePath = join(dynamixBasePath, 'case-model.cfg'); + const customImageFilePath = join(dynamixBasePath, 'case-model.png'); - // If the config file doesn't exist then it's a new OS install - // Default to "default" - if (!existsSync(configFilePath)) { - return { case: states.default }; - } + // If the config file doesn't exist then it's a new OS install + // Default to "default" + if (!existsSync(configFilePath)) { + return { case: states.default }; + } - // Attempt to get case from file - const serverCase = await fs.readFile(configFilePath) - .then(buffer => buffer.toString().split('\n')[0]) - .catch(() => 'error_reading_config_file'); + // Attempt to get case from file + const serverCase = await fs.readFile(configFilePath) + .then(buffer => buffer.toString().split('\n')[0]) + .catch(() => 'error_reading_config_file'); - // Config file can't be read, maybe a permissions issue? - if (serverCase === 'error_reading_config_file') { - return { case: states.couldNotReadConfigFile }; - } + // Config file can't be read, maybe a permissions issue? + if (serverCase === 'error_reading_config_file') { + return { case: states.couldNotReadConfigFile }; + } - // Custom icon - if (serverCase.includes('.')) { - // Ensure image exists - if (!existsSync(customImageFilePath)) { - return { case: states.imageMissing }; - } + // Custom icon + if (serverCase.includes('.')) { + // Ensure image exists + if (!existsSync(customImageFilePath)) { + return { case: states.imageMissing }; + } - // Ensure we're within size limits - if (isOverFileSizeLimit(customImageFilePath)) { - graphqlLogger.debug('"custom-case.png" is too big.'); - return { case: states.imageTooBig }; - } + // Ensure we're within size limits + if (isOverFileSizeLimit(customImageFilePath)) { + graphqlLogger.debug('"custom-case.png" is too big.'); + return { case: states.imageTooBig }; + } - try { - // Get image buffer - const fileBuffer = await fs.readFile(customImageFilePath); + try { + // Get image buffer + const fileBuffer = await fs.readFile(customImageFilePath); - // Likely not an actual image - // 73 bytes is close to the smallest we can get https://garethrees.org/2007/11/14/pngcrush/ - if (fileBuffer.length <= 25) { - return { - case: states.couldNotReadImage - } - } + // Likely not an actual image + // 73 bytes is close to the smallest we can get https://garethrees.org/2007/11/14/pngcrush/ + if (fileBuffer.length <= 25) { + return { + case: states.couldNotReadImage + }; + } - return { - case: { - ...states.custom, - base64: fileBuffer.toString('base64'), - url: serverCase - } - }; - } catch (error) { - return { - case: states.couldNotReadImage - } - } - } + return { + case: { + ...states.custom, + base64: fileBuffer.toString('base64'), + url: serverCase + } + }; + } catch (error) { + return { + case: states.couldNotReadImage + }; + } + } - // Blank cfg file? - if (serverCase.trim().length === 0) { - return { - case: states.default - }; - } + // Blank cfg file? + if (serverCase.trim().length === 0) { + return { + case: states.default + }; + } - // Non-custom icon - return { - case: { - ...states.default, - icon: serverCase - } - }; -}; \ No newline at end of file + // Non-custom icon + return { + case: { + ...states.default, + icon: serverCase + } + }; +}; diff --git a/app/graphql/resolvers/query/index.ts b/app/graphql/resolvers/query/index.ts index 2141f4400..2f8930831 100644 --- a/app/graphql/resolvers/query/index.ts +++ b/app/graphql/resolvers/query/index.ts @@ -10,10 +10,10 @@ import servers from './servers'; import vms from './vms'; export const Query = { - display, - info, - online, - vms, - server, - servers + display, + info, + online, + vms, + server, + servers }; diff --git a/app/graphql/resolvers/query/info.ts b/app/graphql/resolvers/query/info.ts index 81af35a66..56bf55ff1 100644 --- a/app/graphql/resolvers/query/info.ts +++ b/app/graphql/resolvers/query/info.ts @@ -1 +1 @@ -export default () => ({}); \ No newline at end of file +export default () => ({}); diff --git a/app/graphql/resolvers/query/online.ts b/app/graphql/resolvers/query/online.ts index 831a62fc6..1b5bd1045 100644 --- a/app/graphql/resolvers/query/online.ts +++ b/app/graphql/resolvers/query/online.ts @@ -3,4 +3,4 @@ * Written by: Alexis Tyler */ -export default () => true; \ No newline at end of file +export default () => true; diff --git a/app/graphql/resolvers/query/server.ts b/app/graphql/resolvers/query/server.ts index ae8c0bfdc..088c828af 100644 --- a/app/graphql/resolvers/query/server.ts +++ b/app/graphql/resolvers/query/server.ts @@ -10,14 +10,14 @@ import { getServers } from '../../schema/utils'; const { ensurePermission } = utils; export default async (_: unknown, { name }, context: Context) => { - ensurePermission(context.user, { - resource: 'servers', - action: 'read', - possession: 'any' - }); + ensurePermission(context.user, { + resource: 'servers', + action: 'read', + possession: 'any' + }); - const servers = await getServers().catch(() => []); + const servers = await getServers().catch(() => []); - // Single server - return servers.find(server => server.name === name); + // Single server + return servers.find(server => server.name === name); }; diff --git a/app/graphql/resolvers/query/servers.ts b/app/graphql/resolvers/query/servers.ts index 64125551b..be402a8e8 100644 --- a/app/graphql/resolvers/query/servers.ts +++ b/app/graphql/resolvers/query/servers.ts @@ -9,13 +9,13 @@ import { getServers } from '../../schema/utils'; const { ensurePermission } = utils; -export default (_: unknown, __: unknown, context: Context) => { - ensurePermission(context.user, { - resource: 'servers', - action: 'read', - possession: 'any' - }); - - // All servers - return getServers().catch(() => []); +export default async (_: unknown, __: unknown, context: Context) => { + ensurePermission(context.user, { + resource: 'servers', + action: 'read', + possession: 'any' + }); + + // All servers + return getServers().catch(() => []); }; diff --git a/app/graphql/resolvers/query/vms.ts b/app/graphql/resolvers/query/vms.ts index 16cbb0254..755f59049 100644 --- a/app/graphql/resolvers/query/vms.ts +++ b/app/graphql/resolvers/query/vms.ts @@ -3,4 +3,4 @@ * Written by: Alexis Tyler */ -export default () => ({}); \ No newline at end of file +export default () => ({}); diff --git a/app/graphql/resolvers/subscription/index.ts b/app/graphql/resolvers/subscription/index.ts index 21350e3f6..d09a7d692 100644 --- a/app/graphql/resolvers/subscription/index.ts +++ b/app/graphql/resolvers/subscription/index.ts @@ -10,80 +10,80 @@ import { createSubscription, Context } from '../../schema/utils'; const { PluginError } = errors; export const Subscription = { - display: { - ...createSubscription('display') - }, - apikeys: { - // Not sure how we're going to secure this - // ...createSubscription('apikeys') - }, - array: { - ...createSubscription('array') - }, - // devices: { - // ...createSubscription('device') - // }, - dockerContainers: { - ...createSubscription('docker/container') - }, - dockerNetworks: { - ...createSubscription('docker/network') - }, - info: { - ...createSubscription('info') - }, - ping: { - // subscribe: (_, __, context) => { - // // startPing(); - // hasSubscribedToChannel(context.websocketId, 'ping'); - // return pubsub.asyncIterator('ping'); - // } - }, - services: { - ...createSubscription('services') - }, - servers: { - ...createSubscription('servers') - }, - shares: { - ...createSubscription('shares') - }, - unassignedDevices: { - ...createSubscription('devices/unassigned') - }, - uptime: { - ...createSubscription('uptime') - }, - users: { - ...createSubscription('users') - }, - vars: { - ...createSubscription('vars') - }, - vms: { - ...createSubscription('vms/domains') - }, - pluginModule: { - subscribe: async (_: unknown, directiveArgs, context: Context) => { - const { plugin: pluginName, module: pluginModuleName } = directiveArgs; - const channel = `${pluginName}/${pluginModuleName}`; + display: { + ...createSubscription('display') + }, + apikeys: { + // Not sure how we're going to secure this + // ...createSubscription('apikeys') + }, + array: { + ...createSubscription('array') + }, + // Devices: { + // ...createSubscription('device') + // }, + dockerContainers: { + ...createSubscription('docker/container') + }, + dockerNetworks: { + ...createSubscription('docker/network') + }, + info: { + ...createSubscription('info') + }, + ping: { + // Subscribe: (_, __, context) => { + // // startPing(); + // hasSubscribedToChannel(context.websocketId, 'ping'); + // return pubsub.asyncIterator('ping'); + // } + }, + services: { + ...createSubscription('services') + }, + servers: { + ...createSubscription('servers') + }, + shares: { + ...createSubscription('shares') + }, + unassignedDevices: { + ...createSubscription('devices/unassigned') + }, + uptime: { + ...createSubscription('uptime') + }, + users: { + ...createSubscription('users') + }, + vars: { + ...createSubscription('vars') + }, + vms: { + ...createSubscription('vms/domains') + }, + pluginModule: { + subscribe: async (_: unknown, directiveArgs, context: Context) => { + const { plugin: pluginName, module: pluginModuleName } = directiveArgs; + const channel = `${pluginName}/${pluginModuleName}`; - // Verify plugin is installed and active - if (!pluginManager.isInstalled(pluginName, pluginModuleName)) { - throw new PluginError('Plugin not installed.', 500); - } + // Verify plugin is installed and active + if (!pluginManager.isInstalled(pluginName, pluginModuleName)) { + throw new PluginError('Plugin not installed.', 500); + } - if (!pluginManager.isActive(pluginName, pluginModuleName)) { - throw new PluginError('Plugin disabled.', 500); - } + if (!pluginManager.isActive(pluginName, pluginModuleName)) { + throw new PluginError('Plugin disabled.', 500); + } - // It's up to the plugin to publish new data as needed - // so we'll just return the Iterator - hasSubscribedToChannel(context.websocketId, channel); - return pubsub.asyncIterator(channel); - } - }, - online: { - ...createSubscription('online') - } -}; \ No newline at end of file + // It's up to the plugin to publish new data as needed + // so we'll just return the Iterator + hasSubscribedToChannel(context.websocketId, channel); + return pubsub.asyncIterator(channel); + } + }, + online: { + ...createSubscription('online') + } +}; diff --git a/app/graphql/resolvers/user-account.ts b/app/graphql/resolvers/user-account.ts index 21e1c883f..c104c305a 100644 --- a/app/graphql/resolvers/user-account.ts +++ b/app/graphql/resolvers/user-account.ts @@ -4,12 +4,12 @@ */ export const UserAccount = { - __resolveType(obj) { - // Only a user has a password field, the current user aka "me" doesn't. - if (obj.password) { - return 'User'; - } + __resolveType(obj) { + // Only a user has a password field, the current user aka "me" doesn't. + if (obj.password) { + return 'User'; + } - return 'Me'; - } -}; \ No newline at end of file + return 'Me'; + } +}; diff --git a/app/graphql/schema/utils.ts b/app/graphql/schema/utils.ts index 1b28d44d5..3412e31eb 100644 --- a/app/graphql/schema/utils.ts +++ b/app/graphql/schema/utils.ts @@ -6,7 +6,7 @@ import { pubsub, utils, errors, states, apiManager, graphqlLogger } from '../../core'; import { hasSubscribedToChannel } from '../../ws'; import { userCache, CachedServer, CachedServers } from '../../cache'; -import { getServers as getUserServers } from '../../utils' +import { getServers as getUserServers } from '../../utils'; const { varState, networkState } = states; @@ -16,7 +16,7 @@ const { AppError } = errors; export interface Context { user: any; websocketId: string; -}; +} /** * Create a pubsub subscription. diff --git a/app/index.ts b/app/index.ts index 98b5bcf00..4d3673134 100644 --- a/app/index.ts +++ b/app/index.ts @@ -9,14 +9,17 @@ import * as Sentry from '@sentry/node'; import { core, loadServer, states } from './core'; import { server } from './server'; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { version } = require('../package.json') as { version: string }; + // Send errors to server if enabled Sentry.init({ dsn: process.env.SENTRY_DSN, tracesSampleRate: 1.0, - release: `unraid-api@${require('../package.json').version}`, + release: `unraid-api@${version}`, environment: process.env.ENVIRONMENT ?? 'unknown', serverName: os.hostname(), - enabled: Boolean(process.env.SENTRY_DSN) + enabled: Boolean(process.env.SENTRY_DSN) }); // Set user's ID to their flashGuid @@ -39,14 +42,13 @@ am(async () => { server.stop(async () => { /** * Flush messages to server before stopping. - * + * * This may mean waiting up to 5s * before the server actually stops. */ await Sentry.flush(5000); // Kill application - // eslint-disable-next-line unicorn/no-process-exit process.exit(1); }); -}); \ No newline at end of file +}); diff --git a/app/mothership/custom-socket.ts b/app/mothership/custom-socket.ts index c6b07fb16..80ba2b870 100644 --- a/app/mothership/custom-socket.ts +++ b/app/mothership/custom-socket.ts @@ -7,7 +7,7 @@ import { sleep } from '../core/utils'; import { backoff } from './utils'; export interface WebSocketWithHeartBeat extends WebSocket { - heartbeat?: NodeJS.Timeout + heartbeat?: NodeJS.Timeout; } function heartbeat(this: WebSocketWithHeartBeat) { @@ -22,87 +22,87 @@ function heartbeat(this: WebSocketWithHeartBeat) { this.heartbeat = setTimeout(() => { this.terminate(); }, 30000 + 1000); -}; +} interface Options { - name: string; - uri: string; - apiKey: string; - logger: typeof log; + name: string; + uri: string; + apiKey: string; + logger: typeof log; lazy: boolean; - wsServer: WebsocketServer + wsServer: WebsocketServer; } export class CustomSocket { - public name: string; + public name: string; public uri: string; public connection?: WebSocketWithHeartBeat; - protected apiKey: string; - protected logger: typeof log; - protected connectionAttempts = 0; + protected apiKey: string; + protected logger: typeof log; + protected connectionAttempts = 0; private lock?: MutexInterface; - constructor(public options: Partial = {}) { - this.name = options.name ?? 'CustomSocket'; - this.uri = options.uri ?? 'localhost'; - this.apiKey = options.apiKey ?? ''; - this.logger = options.logger ?? log; + constructor(public options: Partial = {}) { + this.name = options.name ?? 'CustomSocket'; + this.uri = options.uri ?? 'localhost'; + this.apiKey = options.apiKey ?? ''; + this.logger = options.logger ?? log; - // Connect right away - if (!options.lazy) { - this.connect(); - } - } + // Connect right away + if (!options.lazy) { + this.connect(); + } + } - public isConnected() { - return this.connection && (this.connection.readyState === this.connection.OPEN); - } - - public isConnecting() { - return this.connection && (this.connection.readyState === this.connection.CONNECTING); - } + public isConnected() { + return this.connection && (this.connection.readyState === this.connection.OPEN); + } - public onError() { + public isConnecting() { + return this.connection && (this.connection.readyState === this.connection.CONNECTING); + } + + public onError() { return (error: NodeJS.ErrnoException) => { this.logger.error(error); }; - } + } - public onConnect() { - const customSocket = this; - return async function(this: WebSocketWithHeartBeat) { - try { - const apiKey = customSocket.apiKey; - if (!apiKey || (typeof apiKey === 'string' && apiKey.length === 0)) { - throw new AppError('Missing key', 4422); - } + public onConnect() { + const customSocket = this; + return async function (this: WebSocketWithHeartBeat) { + try { + const apiKey = customSocket.apiKey; + if (!apiKey || (typeof apiKey === 'string' && apiKey.length === 0)) { + throw new AppError('Missing key', 4422); + } - customSocket.logger.debug('Connected via %s.', customSocket.connection?.url); + customSocket.logger.debug('Connected via %s.', customSocket.connection?.url); - // Reset connection attempts - customSocket.connectionAttempts = 0; - } catch (error) { - this.close(error.code.length === 4 ? error.code : `4${error.code}`, JSON.stringify({ + // Reset connection attempts + customSocket.connectionAttempts = 0; + } catch (error) { + this.close(error.code.length === 4 ? error.code : `4${error.code}`, JSON.stringify({ message: error.message ?? 'Internal Server Error' - })); - } - }; - } + })); + } + }; + } - protected onDisconnect() { + protected onDisconnect() { const customSocket = this; return async function (this: WebSocketWithHeartBeat, code: number, _message: string) { try { const message = _message.trim() === '' ? { message: '' } : JSON.parse(_message); customSocket.logger.debug('Connection closed with code=%s reason="%s"', code, code === 1006 ? 'Terminated' : message.message); - + // Stop ws heartbeat if (this.heartbeat) { clearTimeout(this.heartbeat); } - + // Http 4XX error if (code >= 4400 && code <= 4499) { // Unauthorized - Invalid/missing API key. @@ -110,36 +110,36 @@ export class CustomSocket { customSocket.logger.debug('Invalid API key, waiting for new key...'); return; } - + // Rate limited if (code === 4429) { try { let interval: NodeJS.Timeout | undefined; const retryAfter = parseInt(message['Retry-After'], 10) || 30; customSocket.logger.debug('Rate limited, retrying after %ss', retryAfter); - + // Less than 30s if (retryAfter <= 30) { let seconds = retryAfter; - + // Print retry once per second interval = setInterval(() => { seconds--; customSocket.logger.debug('Retrying connection in %ss', seconds); }, ONE_SECOND); } - + if (retryAfter >= 1) { await sleep(ONE_SECOND * retryAfter); } - + if (interval) { clearInterval(interval); } - } catch {}; + } catch {} } } - + // We likely closed this // This is usually because the API key is updated if (code === 4200) { @@ -147,7 +147,7 @@ export class CustomSocket { customSocket.connect(); return; } - + // Something went wrong on the connection // Let's wait an extra bit if (code === 4500) { @@ -156,11 +156,11 @@ export class CustomSocket { } catch (error) { customSocket.logger.debug('Connection closed with code=%s reason="%s"', code, error.message); } - + try { // Wait a few seconds await sleep(backoff(customSocket.connectionAttempts, ONE_MINUTE, 5)); - + // Reconnect await customSocket.connect(customSocket.connectionAttempts + 1); } catch (error) { @@ -169,60 +169,60 @@ export class CustomSocket { }; } - public onMessage() { + public onMessage() { const customSocket = this; - return async function(message: string, ...args) { + return async function (message: string, ...args) { customSocket.logger.silly('message="%s" args="%s"', message, ...args); }; - } + } - protected async cleanup() { - // Kill existing socket connection + protected async cleanup() { + // Kill existing socket connection if (this.connection) { this.connection.close(4200, JSON.stringify({ message: 'Reconnecting' })); } - } + } - protected async getApiKey() { - return ''; - } + protected async getApiKey() { + return ''; + } - protected async getHeaders() { - return {}; - } + protected async getHeaders() { + return {}; + } - protected async isConnectionAllowed() { - return true; - } + protected async isConnectionAllowed() { + return true; + } - protected async sendMessage(client?: WebSocketWithHeartBeat, message?: string, timeout = 1000) { + protected async sendMessage(client?: WebSocketWithHeartBeat, message?: string, timeout = 1000) { try { if (!client || client.readyState === 0 || client.readyState === 3) { // Wait for $timeout seconds await sleep(timeout); - + // Retry sending await this.sendMessage(client, message, timeout); return; } - + // Only send when socket is open if (client.readyState === client.OPEN) { client.send(message); this.logger.silly('Message sent to %s.', message, client?.url); return; } - + // Failed replying as socket isn't open this.logger.error('Failed replying to %s. state=%s message="%s"', client?.url, client.readyState, message); } catch (error) { this.logger.error('Failed replying to %s.', client?.url, error); - }; - }; + } + } - private async getLock() { + private async getLock() { if (!this.lock) { this.lock = new Mutex(); } @@ -231,39 +231,39 @@ export class CustomSocket { return { release }; - } + } - private async setRetryAttempt(currentRetryAttempt = 0) { - this.connectionAttempts += 1; + private async setRetryAttempt(currentRetryAttempt = 0) { + this.connectionAttempts += 1; if (currentRetryAttempt >= 1) { this.logger.debug('Connection attempt %s', currentRetryAttempt); } - } + } - private async _connect() { - this.connection = new WebSocket(this.uri, ['graphql-ws'], { - headers: await this.getHeaders() - }); + private async _connect() { + this.connection = new WebSocket(this.uri, ['graphql-ws'], { + headers: await this.getHeaders() + }); this.connection.on('ping', heartbeat.bind(this.connection)); this.connection.on('error', this.onError()); this.connection.on('close', this.onDisconnect()); this.connection.on('open', this.onConnect()); this.connection.on('message', this.onMessage()); - // this.connection.on('ping', console.log); + // This.connection.on('ping', console.log); // this.connection.on('error', console.log); // this.connection.on('close', console.log); // this.connection.on('open', console.log); // this.connection.on('message', console.log); - } + } - public async connect(retryAttempt: number = 0) { - const lock = await this.getLock(); + public async connect(retryAttempt = 0) { + const lock = await this.getLock(); try { // Set retry attempt count - await this.setRetryAttempt(retryAttempt); - - // Get the current apiKey - this.apiKey = await this.getApiKey(); + await this.setRetryAttempt(retryAttempt); + + // Get the current apiKey + this.apiKey = await this.getApiKey(); // Check the connection is allowed await this.isConnectionAllowed(); @@ -282,7 +282,7 @@ export class CustomSocket { lock.release(); } } - + public async disconnect() { const lock = await this.getLock(); try { @@ -290,7 +290,7 @@ export class CustomSocket { // 4200 === ok this.connection.close(4200); } - } catch(error) { + } catch (error) { this.logger.error('Failed disconnecting reason=%s', error.message); } finally { lock.release(); @@ -302,4 +302,4 @@ export class CustomSocket { await sleep(1000); await this.connect(); } -}; +} diff --git a/app/mothership/index.ts b/app/mothership/index.ts index 766f8190b..13f3202a7 100644 --- a/app/mothership/index.ts +++ b/app/mothership/index.ts @@ -1,2 +1,2 @@ -export { MothershipSocket } from './sockets/mothership' -export { InternalGraphql } from './sockets/internal-graphql' +export { MothershipSocket } from './sockets/mothership'; +export { InternalGraphql } from './sockets/internal-graphql'; diff --git a/app/mothership/sockets/internal-graphql.ts b/app/mothership/sockets/internal-graphql.ts index 4c3ae4214..f2409b41e 100644 --- a/app/mothership/sockets/internal-graphql.ts +++ b/app/mothership/sockets/internal-graphql.ts @@ -6,16 +6,16 @@ import { CustomSocket, WebSocketWithHeartBeat } from '../custom-socket'; import { MothershipSocket } from './mothership'; export class InternalGraphql extends CustomSocket { - private mothership?: MothershipSocket; + private readonly mothership?: MothershipSocket; constructor(options: CustomSocket['options'] = {}) { - super({ - name: 'InternalGraphql', - uri: INTERNAL_WS_LINK, + super({ + name: 'InternalGraphql', + uri: INTERNAL_WS_LINK, logger: relayLogger, - ...options - }); - } + ...options + }); + } protected async getApiKey() { const key = apiManager.getKey('my_servers'); @@ -37,7 +37,6 @@ export class InternalGraphql extends CustomSocket { this.close(4200, JSON.stringify({ message: error.emss })); - return; } } }; @@ -53,17 +52,17 @@ export class InternalGraphql extends CustomSocket { // This isn't an actual error so we skip it return; } - + // Socket was still offline try again? if (error.code && ['ENOENT', 'ECONNREFUSED'].includes(error.code)) { // Wait 1s await sleep(1000); - + // Re-connect to internal graphql server internalGraphql.connect(); return; } - + internalGraphql.logger.error(error); }; } @@ -87,4 +86,4 @@ export class InternalGraphql extends CustomSocket { })); }; } -}; \ No newline at end of file +} diff --git a/app/mothership/sockets/mothership.ts b/app/mothership/sockets/mothership.ts index 0800d789f..948b0d06f 100644 --- a/app/mothership/sockets/mothership.ts +++ b/app/mothership/sockets/mothership.ts @@ -9,32 +9,32 @@ import { CustomSocket, WebSocketWithHeartBeat } from '../custom-socket'; import { InternalGraphql } from './internal-graphql'; export class MothershipSocket extends CustomSocket { - private internalGraphqlSocket?: CustomSocket; - private mothershipServersEndpoint?: { - unsubscribe: () => void; - }; + private internalGraphqlSocket?: CustomSocket; + private mothershipServersEndpoint?: { + unsubscribe: () => void; + }; constructor(options: CustomSocket['options'] = {}) { - super({ - name: 'Mothership', - uri: MOTHERSHIP_RELAY_WS_LINK, - logger: mothershipLogger, - lazy: false, - ...options - }); - } + super({ + name: 'Mothership', + uri: MOTHERSHIP_RELAY_WS_LINK, + logger: mothershipLogger, + lazy: false, + ...options + }); + } - private connectToInternalGraphql(options: InternalGraphql['options'] = {}) { - this.internalGraphqlSocket = new InternalGraphql(options); - } + private connectToInternalGraphql(options: InternalGraphql['options'] = {}) { + this.internalGraphqlSocket = new InternalGraphql(options); + } - private async connectToMothershipsGraphql() { - this.mothershipServersEndpoint = await subscribeToServers(this.apiKey); - } + private async connectToMothershipsGraphql() { + this.mothershipServersEndpoint = await subscribeToServers(this.apiKey); + } - private async disconnectFromMothershipsGraphql() { - this.mothershipServersEndpoint?.unsubscribe(); - } + private async disconnectFromMothershipsGraphql() { + this.mothershipServersEndpoint?.unsubscribe(); + } protected async getApiKey() { const key = apiManager.getKey('my_servers'); @@ -43,9 +43,9 @@ export class MothershipSocket extends CustomSocket { } return key.key; - } - - protected async getHeaders() { + } + + protected async getHeaders() { const apiKey = apiManager.getKey('my_servers')?.key!; const keyFile = varState.data?.regFile ? readFileIfExists(varState.data?.regFile).toString('base64') : ''; const serverName = `${varState.data?.name}`; @@ -53,49 +53,49 @@ export class MothershipSocket extends CustomSocket { const machineId = `${await getMachineId()}`; return { - 'x-api-key': apiKey, - 'x-flash-guid': varState.data?.flashGuid ?? '', - 'x-key-file': keyFile ?? '', - 'x-server-name': serverName, - 'x-lan-ip': lanIp, - 'x-machine-id': machineId - }; - } + 'x-api-key': apiKey, + 'x-flash-guid': varState.data?.flashGuid ?? '', + 'x-key-file': keyFile ?? '', + 'x-server-name': serverName, + 'x-lan-ip': lanIp, + 'x-machine-id': machineId + }; + } onConnect() { - const mothership = this; - const onConnect = super.onConnect; - return async function(this: WebSocketWithHeartBeat) { - try { - // Run super - onConnect(); + const mothership = this; + const onConnect = super.onConnect; + return async function (this: WebSocketWithHeartBeat) { + try { + // Run super + onConnect(); - // Connect to local graphql - mothership.connectToInternalGraphql(); + // Connect to local graphql + mothership.connectToInternalGraphql(); - // Sub to /servers on mothership - mothership.connectToMothershipsGraphql(); - } catch (error) { - this.close(error.code.length === 4 ? error.code : `4${error.code}`, JSON.stringify({ - message: error.message ?? 'Internal Server Error' - })); - } + // Sub to /servers on mothership + mothership.connectToMothershipsGraphql(); + } catch (error) { + this.close(error.code.length === 4 ? error.code : `4${error.code}`, JSON.stringify({ + message: error.message ?? 'Internal Server Error' + })); + } }; - } + } - onDisconnect() { - const mothership = this; - const onDisconnect = super.onDisconnect; + onDisconnect() { + const mothership = this; + const onDisconnect = super.onDisconnect; return async function (this: WebSocketWithHeartBeat, code: number, _message: string) { try { - // Close connection to local graphql endpoint - mothership.internalGraphqlSocket?.connection?.close(200); - - // Close connection to motherships's server's endpoint - mothership.disconnectFromMothershipsGraphql(); + // Close connection to local graphql endpoint + mothership.internalGraphqlSocket?.connection?.close(200); - // Process disconnection - onDisconnect(); + // Close connection to motherships's server's endpoint + mothership.disconnectFromMothershipsGraphql(); + + // Process disconnection + onDisconnect(); } catch (error) { mothership.logger.debug('Connection closed with code=%s reason="%s"', code, error.message); } @@ -118,38 +118,38 @@ export class MothershipSocket extends CustomSocket { onError() { const mothership = this; - return async function(this: WebSocketWithHeartBeat, error: NodeJS.ErrnoException) { - try { - mothership.logger.error(error); + return async function (this: WebSocketWithHeartBeat, error: NodeJS.ErrnoException) { + try { + mothership.logger.error(error); - // The relay is down - if (error.message.includes('502')) { - // Sleep for 30 seconds - await sleep(ONE_MINUTE / 2); - } + // The relay is down + if (error.message.includes('502')) { + // Sleep for 30 seconds + await sleep(ONE_MINUTE / 2); + } - // Connection refused, aka couldn't connect - // This is usually because the address is wrong or offline - if (error.code === 'ECONNREFUSED') { - // @ts-expect-error - mothership.logger.debug(`Couldn't connect to %s:%s`, error.address, error.port); - return; - } + // Connection refused, aka couldn't connect + // This is usually because the address is wrong or offline + if (error.code === 'ECONNREFUSED') { + // @ts-expect-error + mothership.logger.debug('Couldn\'t connect to %s:%s', error.address, error.port); + return; + } - // Closed before connection started - if (error.toString().includes('WebSocket was closed before the connection was established')) { - mothership.logger.debug(error.message); - return; - } + // Closed before connection started + if (error.toString().includes('WebSocket was closed before the connection was established')) { + mothership.logger.debug(error.message); + return; + } - throw error; - } catch { - // Unknown error - mothership.logger.error('socket error', error); - } finally { - // Kick the connection - this.close(4500, JSON.stringify({ message: error.message })); - } - }; - } -}; \ No newline at end of file + throw error; + } catch { + // Unknown error + mothership.logger.error('socket error', error); + } finally { + // Kick the connection + this.close(4500, JSON.stringify({ message: error.message })); + } + }; + } +} diff --git a/app/mothership/subscribe-to-servers.ts b/app/mothership/subscribe-to-servers.ts index cd3317410..1083b52e0 100644 --- a/app/mothership/subscribe-to-servers.ts +++ b/app/mothership/subscribe-to-servers.ts @@ -4,62 +4,62 @@ import { MOTHERSHIP_GRAPHQL_LINK, ONE_SECOND } from '../consts'; import { userCache, CachedServers } from '../cache'; import { log as logger } from '../core'; -const log = logger.createChild({ prefix: 'subscribe-to-servers'}); +const log = logger.createChild({ prefix: 'subscribe-to-servers' }); const client = new SubscriptionClient(MOTHERSHIP_GRAPHQL_LINK, { - reconnect: true, - lazy: true, - minTimeout: ONE_SECOND * 30, - connectionCallback: (errors) => { - try { - if (errors) { - // Log all errors - errors.forEach((error: any) => { - // [error] {"message":"","locations":[{"line":2,"column":13}],"path":["servers"],"extensions":{"code":"INTERNAL_SERVER_ERROR","exception":{"fatal":false,"extras":{},"name":"AppError","status":500}}} [./dist/index.js:24646] - log.error('Failed connecting to %s code=%s reason="%s"', MOTHERSHIP_GRAPHQL_LINK, error.extensions.code, error.message); - }); - } - } catch {} - } + reconnect: true, + lazy: true, + minTimeout: ONE_SECOND * 30, + connectionCallback: errors => { + try { + if (errors) { + // Log all errors + errors.forEach((error: any) => { + // [error] {"message":"","locations":[{"line":2,"column":13}],"path":["servers"],"extensions":{"code":"INTERNAL_SERVER_ERROR","exception":{"fatal":false,"extras":{},"name":"AppError","status":500}}} [./dist/index.js:24646] + log.error('Failed connecting to %s code=%s reason="%s"', MOTHERSHIP_GRAPHQL_LINK, error.extensions.code, error.message); + }); + } + } catch {} + } }); export const subscribeToServers = async (apiKey: string) => { - log.silly('Subscribing to servers with %s', apiKey); - const query = client.request({ - query: `subscription servers ($apiKey: String!) { + log.silly('Subscribing to servers with %s', apiKey); + const query = client.request({ + query: `subscription servers ($apiKey: String!) { servers @auth(apiKey: $apiKey) }`, - variables: { - apiKey - } - }); + variables: { + apiKey + } + }); - // Subscribe - const subscription = query.subscribe({ - next: ({ data, errors }) => { - log.silly('Got data back with %s errors', errors?.length ?? 0); - log.silly('Got data %s', data); - log.silly('Got errors %s', errors); + // Subscribe + const subscription = query.subscribe({ + next: ({ data, errors }) => { + log.silly('Got data back with %s errors', errors?.length ?? 0); + log.silly('Got data %s', data); + log.silly('Got errors %s', errors); - if (errors) { - // Log all errors - errors.forEach((error: any) => { - log.error('Failed subscribing to %s code=%s reason="%s"', MOTHERSHIP_GRAPHQL_LINK, error.extensions.code, error.message); - }); + if (errors) { + // Log all errors + errors.forEach((error: any) => { + log.error('Failed subscribing to %s code=%s reason="%s"', MOTHERSHIP_GRAPHQL_LINK, error.extensions.code, error.message); + }); - return; - } - - // Update internal cache - userCache.set('mine', { - servers: data.servers - }); - - // Update subscribers - pubsub.publish('servers', { - servers: data.servers - }); - } - }); + return; + } - return subscription; + // Update internal cache + userCache.set('mine', { + servers: data.servers + }); + + // Update subscribers + pubsub.publish('servers', { + servers: data.servers + }); + } + }); + + return subscription; }; diff --git a/app/mothership/utils.ts b/app/mothership/utils.ts index 9a411ded0..6d35353c4 100644 --- a/app/mothership/utils.ts +++ b/app/mothership/utils.ts @@ -16,7 +16,7 @@ export const applyJitter = (value: number) => { }; export const backoff = (attempt: number, maxDelay: number, multiplier: number) => { - const delay = applyJitter((Math.pow(2.0, attempt) - 1.0) * 0.5); + const delay = applyJitter(Math.pow(2.0, attempt - 1.0) * 0.5); return Math.round(Math.min(delay * multiplier, maxDelay)); }; @@ -26,4 +26,4 @@ export const readFileIfExists = (filePath: string) => { } catch {} return Buffer.from(''); -}; \ No newline at end of file +}; diff --git a/app/run.ts b/app/run.ts index 0749cb1d5..81b4435e8 100644 --- a/app/run.ts +++ b/app/run.ts @@ -5,68 +5,69 @@ import { debugTimer } from './core/utils'; /** * Publish update to topic channel. */ -export const publish = (channel: string, mutation: string, node?: {}) => { - if (!node) { - throw new Error('Data missing?'); - } +export const publish = async (channel: string, mutation: string, node?: Record) => { + if (!node) { + throw new Error('Data missing?'); + } - const data = { - [channel]: { - mutation, - node - } - }; + const data = { + [channel]: { + mutation, + node + } + }; - // Update clients - const fieldName = Object.keys(data)[0]; - pubsub.publish(channel, { - [fieldName]: data[fieldName].node - }); + // Update clients + const fieldName = Object.keys(data)[0]; + await pubsub.publish(channel, { + [fieldName]: data[fieldName].node + }); }; interface RunOptions { - node?: {} - moduleToRun?: Function - context?: any + node?: Record; + moduleToRun?: (context: any) => CoreResult; + context?: any; } /** * Run a module. */ export const run = async (channel: string, mutation: string, options: RunOptions) => { - const { - node, - moduleToRun, - context - } = options; + const { + node, + moduleToRun, + context + } = options; - if (!moduleToRun) { - coreLogger.silly('Tried to run but has no "moduleToRun"'); - return publish(channel, mutation, node); - } + if (!moduleToRun) { + coreLogger.silly('Tried to run but has no "moduleToRun"'); + await publish(channel, mutation, node); + return; + } - try { - // Run module - const result: CoreResult = await new Promise(resolve => { - debugTimer(`run:${moduleToRun.name}`); - return resolve(moduleToRun(context)); - }); + try { + // Run module + const result: CoreResult = await new Promise(resolve => { + debugTimer(`run:${moduleToRun.name}`); + resolve(moduleToRun(context)); + }); - // Log result - coreLogger.silly(`run:${moduleToRun.name}`, JSON.stringify(result.json)); + // Log result + coreLogger.silly(`run:${moduleToRun.name}`, JSON.stringify(result.json)); - // Save result - publish(channel, mutation, result.json); - } catch (error) { - // Ensure we aren't leaking anything in production - if (process.env.NODE_ENV === 'production') { - coreLogger.debug('Error:', error.message); - } else { - const logger = coreLogger[error.status && error.status >= 400 ? 'error' : 'warn'].bind(coreLogger); - logger('Error:', error.message); - } - } + // Save result + publish(channel, mutation, result.json); + } catch (error: any) { + // Ensure we aren't leaking anything in production + if (process.env.NODE_ENV === 'production') { + coreLogger.debug('Error:', error.message); + } else { + const logger = coreLogger[error.status && error.status >= 400 ? 'error' : 'warn'].bind(coreLogger); + logger('Error:', error.message); + } + } - debugTimer(`run:${moduleToRun.name}`); + debugTimer(`run:${moduleToRun.name}`); }; diff --git a/app/server.ts b/app/server.ts index 1130a1af0..68d3876d0 100644 --- a/app/server.ts +++ b/app/server.ts @@ -23,7 +23,7 @@ const configFilePath = path.join(paths.get('dynamix-base')!, 'case-model.cfg'); const customImageFilePath = path.join(paths.get('dynamix-base')!, 'case-model.png'); const updatePubsub = async () => { - pubsub.publish('display', { + await pubsub.publish('display', { display: await display() }); }; @@ -65,10 +65,10 @@ if (process.env.ENVIRONMENT !== 'production') { if (!app.get('x-environment')) { app.set('x-environment', process.env.ENVIRONMENT); } - + // Update header with current environment res.set('x-environment', app.get('x-environment')); - + next(); }); } @@ -76,7 +76,7 @@ if (process.env.ENVIRONMENT !== 'production') { // Mount graph endpoint // @ts-expect-error const graphApp = new ApolloServer(graphql); -graphApp.applyMiddleware({app}); +graphApp.applyMiddleware({ app }); // List all endpoints at start of server app.get('/', (_, res) => { @@ -84,7 +84,7 @@ app.get('/', (_, res) => { }); // Handle errors by logging them and returning a 500. -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-var-requires app.use((error, _, res, __) => { log.error(error); if (error.stack) { @@ -101,7 +101,7 @@ const stoppableServer = stoppable(httpServer); if (isNaN(parseInt(port, 10))) { stoppableServer.on('listening', () => { // Set permissions - return fs.chmodSync(port, 660); + fs.chmodSync(port, 660); }); stoppableServer.on('error', async (error: NodeJS.ErrnoException) => { @@ -109,7 +109,7 @@ if (isNaN(parseInt(port, 10))) { coreLogger.error(error); throw error; } - + // Check if port is unix socket or numbered port // If it's a numbered port then throw if (!isNaN(parseInt(port, 10))) { @@ -118,7 +118,7 @@ if (isNaN(parseInt(port, 10))) { // Check if the process that made this file is still alive const pid = await execa.command(`lsof -t ${port}`) - .then(output => { + .then(output => { const pids = cleanStdout(output).split('\n'); return pids[0]; }).catch(() => undefined); @@ -134,7 +134,7 @@ if (isNaN(parseInt(port, 10))) { // Stop the server stoppableServer.close(); - + // Restart the server net.connect({ path: port @@ -150,13 +150,13 @@ if (isNaN(parseInt(port, 10))) { if (error.code !== 'ECONNREFUSED') { log.error(error); - + process.exitCode = 1; } - + // Not in use: delete it and re-listen fs.unlinkSync(port); - + setTimeout(() => { stoppableServer.listen(port); }, 1000); @@ -210,18 +210,19 @@ export const server = { connectedAtleastOnce = true; await sleep(1000); } + await mothership.connect(); }); // Start http server return stoppableServer.listen(port, () => { // Downgrade process user to owner of this file - return fs.stat(__filename, (error, stats) => { + fs.stat(__filename, (error, stats) => { if (error) { throw error; } - return process.setuid(stats.uid); + process.setuid(stats.uid); }); }); }, @@ -241,10 +242,11 @@ export const server = { // Run callback if (callback) { - return callback(); + callback(); + return; } // Gracefully exit exitApp(); } -}; \ No newline at end of file +}; diff --git a/app/utils.ts b/app/utils.ts index 10cbb571e..043ceb582 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -3,27 +3,28 @@ import * as Sentry from '@sentry/node'; import { MOTHERSHIP_GRAPHQL_LINK } from './consts'; import { CachedServer } from './cache'; -export const getServers = (apiKey: string) => fetch(MOTHERSHIP_GRAPHQL_LINK, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - body: JSON.stringify({ - query: 'query($apiKey: String!) { servers @auth(apiKey: $apiKey) { owner { username url avatar } guid apikey name status wanip lanip localurl remoteurl } }', - variables: { - apiKey - } - }) +export const getServers = async (apiKey: string) => fetch(MOTHERSHIP_GRAPHQL_LINK, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json' + }, + body: JSON.stringify({ + query: 'query($apiKey: String!) { servers @auth(apiKey: $apiKey) { owner { username url avatar } guid apikey name status wanip lanip localurl remoteurl } }', + variables: { + apiKey + } + }) }) -.then(async response => { - const { data, errors } = await response.json(); - if (errors) { - return new Error(errors[0].message); - } - return data.servers as Promise; -}) -.catch(error => { - Sentry.captureException(error); - return error; -}); \ No newline at end of file + .then(async response => { + const { data, errors } = await response.json(); + if (errors) { + return new Error(errors[0].message); + } + + return data.servers as Promise; + }) + .catch(error => { + Sentry.captureException(error); + return error; + }); diff --git a/app/ws.ts b/app/ws.ts index f2f842556..5cd924351 100644 --- a/app/ws.ts +++ b/app/ws.ts @@ -1,50 +1,50 @@ interface subscription { - total: number - channels: string[] + total: number; + channels: string[]; } -const subscriptions: { - [key: string]: subscription -} = {}; +const subscriptions: Record = {}; /** * Return current ws connection count. */ export const getWsConectionCount = () => { - return Object.values(subscriptions).filter(subscription => subscription.total >= 1).length; + return Object.values(subscriptions).filter(subscription => subscription.total >= 1).length; }; /** * Return current ws connection count in channel. */ export const getWsConectionCountInChannel = (channel: string) => { - return Object.values(subscriptions).filter(subscription => subscription.channels.includes(channel)).length; + return Object.values(subscriptions).filter(subscription => subscription.channels.includes(channel)).length; }; export const hasSubscribedToChannel = (id: string, channel: string) => { - // Setup inital object - if (subscriptions[id] === undefined) { - subscriptions[id] = { - total: 1, - channels: [channel] - }; - return; - } - subscriptions[id].total++; - subscriptions[id].channels.push(channel); + // Setup inital object + if (subscriptions[id] === undefined) { + subscriptions[id] = { + total: 1, + channels: [channel] + }; + return; + } + + subscriptions[id].total++; + subscriptions[id].channels.push(channel); }; export const hasUnsubscribedFromChannel = (id: string, channel: string) => { - // Setup inital object - if (subscriptions[id] === undefined) { - subscriptions[id] = { - total: 0, - channels: [] - }; - return; - } - subscriptions[id].total--; - subscriptions[id].channels = subscriptions[id].channels.filter(existingChannel => existingChannel !== channel); + // Setup inital object + if (subscriptions[id] === undefined) { + subscriptions[id] = { + total: 0, + channels: [] + }; + return; + } + + subscriptions[id].total--; + subscriptions[id].channels = subscriptions[id].channels.filter(existingChannel => existingChannel !== channel); }; /** @@ -53,10 +53,10 @@ export const hasUnsubscribedFromChannel = (id: string, channel: string) => { * @param ws */ export const wsHasConnected = (id: string) => { - subscriptions[id] = { - total: 0, - channels: [] - }; + subscriptions[id] = { + total: 0, + channels: [] + }; }; /** @@ -65,6 +65,6 @@ export const wsHasConnected = (id: string) => { * @param ws */ export const wsHasDisconnected = (id: string) => { - subscriptions[id].total = 0; - subscriptions[id].channels = []; + subscriptions[id].total = 0; + subscriptions[id].channels = []; }; diff --git a/package.json b/package.json index fa0916154..2e2691f1b 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,7 @@ "bundle-dependencies": "^1.0.2", "cpx": "1.5.0", "cz-conventional-changelog": "3.3.0", + "eslint": "^7.18.0", "husky": "4.3.8", "modclean": "^3.0.0-beta.1", "node-env-run": "^4.0.2", diff --git a/tsconfig.json b/tsconfig.json index b0e832902..ee462f9de 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -68,6 +68,6 @@ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, } } \ No newline at end of file