chore: lint

This commit is contained in:
Alexis Tyler
2021-01-29 12:03:26 +10:30
parent 6a8f4ffd33
commit 855ba2fc75
42 changed files with 1124 additions and 2535 deletions

View File

@@ -1,5 +1,5 @@
import stw from 'spread-the-word';
import { log, discoveryLogger } from '../log';
import { discoveryLogger as log } from '../log';
/**
* Listen to devices on the local network via mDNS.
@@ -17,13 +17,13 @@ export const listen = async () => {
return;
}
discoveryLogger.info(`Found a new local server [${ipAddress}], visit your my servers dashboard to claim.`);
log.info(`Found a new local server [${ipAddress}], visit your my servers dashboard to claim.`);
}
}
// Console.log(`${service.name} is up! (from ${referrer.address}`);
})
.on('down', (remoteService, _res, referrer) => {
discoveryLogger.debug(`${remoteService.name} is down! (from ${referrer.address})`);
log.debug(`${remoteService.name} is down! (from ${referrer.address})`);
});
await stw.listen();

View File

@@ -11,6 +11,6 @@ import { AppError } from './app-error';
export class FieldMissingError extends AppError {
constructor(private readonly field: string) {
// Overriding both message and status code.
super('Field missing: ' + field, 400);
super(`Field missing: ${field}`, 400);
}
}

View File

@@ -9,7 +9,11 @@ import { sharesState, slotsState } from '../states';
import { ensurePermission } from '../utils';
export const addShare = async (context: CoreContext): Promise<CoreResult> => {
const { user, data = {} } = context;
const { user, data } = context;
if (!data?.name) {
throw new AppError('No name provided');
}
// Check permissions
ensurePermission(user, {
@@ -18,7 +22,7 @@ export const addShare = async (context: CoreContext): Promise<CoreResult> => {
possession: 'any'
});
const { name } = data;
const name: string = data.name;
const userShares = sharesState.find().map(({ name }) => name);
const diskShares = slotsState.find({ exportable: 'yes' }).filter(({ name }) => name.startsWith('disk')).map(({ name }) => name);

View File

@@ -23,7 +23,7 @@ export const getArray = (context: CoreContext): CoreResult => {
// Array state
const arrayState = varState?.data?.mdState.toLowerCase();
const state = arrayState.startsWith('error') ? arrayState.split(':')[1] : arrayState;
const state: string = arrayState.startsWith('error') ? arrayState.split(':')[1] : arrayState;
// All known disks
const allDisks = slotsState.find().filter(disk => disk.device);

View File

@@ -8,8 +8,7 @@ import { CoreResult, CoreContext } from '../types';
import { paths } from '../paths';
import { FileMissingError } from '../errors';
import { ensurePermission } from '../utils';
const Table = require('cli-table');
import Table from 'cli-table';
/**
* Get parity history.

View File

@@ -86,13 +86,13 @@ const systemPciDevices = async (): Promise<PciDevice[]> => {
* - Add whether kernel-bound driver exists
* - Cleanup device vendor/product names
*/
const processedDevices = await filterDevices(filteredDevices).then(devices => {
return devices
const processedDevices = await filterDevices(filteredDevices).then(async devices => {
return Promise.all(devices
// @ts-expect-error
.map(addDeviceClass)
.map(device => {
.map(async device => {
// Attempt to get the current kernel-bound driver for this pci device
isSymlink(`${basePath}${device.id}/driver`).then(symlink => {
await isSymlink(`${basePath}${device.id}/driver`).then(symlink => {
if (symlink) {
// $strLink = @readlink('/sys/bus/pci/devices/0000:'.$arrMatch['id']. '/driver');
// if (!empty($strLink)) {
@@ -106,7 +106,7 @@ const systemPciDevices = async (): Promise<PciDevice[]> => {
device.productname = sanitizeProduct(device.productname);
return device;
});
}));
});
return processedDevices;
@@ -144,6 +144,7 @@ const getSystemUSBDevices = async (): Promise<any[]> => {
// 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 => {
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
const [, id] = line.match(/usb:v(\w{9})/)!;
return id.replace('p', ':');
});
@@ -175,6 +176,7 @@ const getSystemUSBDevices = async (): Promise<any[]> => {
// Parse the line
const [, _] = line.split(/[ \t]{2,}/).filter(Boolean);
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
const match = _.match(/^(\S+)\s(.*)/)?.slice(1);
// If there's no match return nothing

View File

@@ -14,6 +14,7 @@ export interface Options extends NotifierOptions {}
export class HttpNotifier extends Notifier {
readonly $http = fetch;
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
constructor(options: Options) {
super(options);
}

View File

@@ -28,6 +28,7 @@ export class MqttNotifier extends Notifier {
super(options);
if (MqttNotifier.instance) {
// eslint-disable-next-line no-constructor-return
return MqttNotifier.instance;
}

View File

@@ -152,6 +152,7 @@ export class PluginManager {
}
// Get the plugin's package.json
// eslint-disable-next-line @typescript-eslint/no-var-requires
const pluginPackage: PackageJson = require(pluginPackagePath);
if (!pluginPackage.main) {
throw new AppError(`Plugin "${pluginName}" is missing its "main" field in the "package.json".`);

View File

@@ -10,7 +10,9 @@ import { parseConfig } from '../utils/misc';
import { ArrayState } from './state';
import { LooseObject } from '../types';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface Device {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface DeviceIni {}
const parse = (state: DeviceIni[]): Device[] => Object.values(state);
@@ -19,14 +21,15 @@ const parse = (state: DeviceIni[]): Device[] => Object.values(state);
* Devices
*/
class Devices extends ArrayState {
public channel = 'devices';
private static instance: Devices;
public channel = 'devices';
_data: any;
constructor() {
super();
if (Devices.instance) {
// eslint-disable-next-line no-constructor-return
return Devices.instance;
}

View File

@@ -71,6 +71,7 @@ class Network extends ArrayState {
super();
if (Network.instance) {
// eslint-disable-next-line no-constructor-return
return Network.instance;
}

View File

@@ -27,13 +27,14 @@ const parse = (state: SecIni[]) => {
};
class NfsSec extends ArrayState {
public channel = 'nsf-sec';
private static instance: NfsSec;
public channel = 'nsf-sec';
constructor() {
super();
if (NfsSec.instance) {
// eslint-disable-next-line no-constructor-return
return NfsSec.instance;
}

View File

@@ -41,13 +41,14 @@ const parse = (state: SharesIni[]): Share[] => {
};
class Shares extends ArrayState {
public channel = 'shares';
private static instance: Shares;
public channel = 'shares';
constructor() {
super();
if (Shares.instance) {
// eslint-disable-next-line no-constructor-return
return Shares.instance;
}

View File

@@ -59,7 +59,7 @@ const parse = (state: SlotIni[]) => {
fsSize: toNumber(slot.fsSize),
fsFree: toNumber(slot.fsFree),
exportable: slot.exportable === 'e',
fsColor: slot.fsColor && slot.fsColor.replace('-', '_')
fsColor: slot.fsColor.replace('-', '_')
};
return result;
@@ -77,6 +77,7 @@ class Slots extends ArrayState {
super();
if (Slots.instance) {
// eslint-disable-next-line no-constructor-return
return Slots.instance;
}

View File

@@ -66,6 +66,7 @@ class SmbSec extends ArrayState {
super();
if (SmbSec.instance) {
// eslint-disable-next-line no-constructor-return
return SmbSec.instance;
}

View File

@@ -43,6 +43,7 @@ class Users extends ArrayState {
super();
if (Users.instance) {
// eslint-disable-next-line no-constructor-return
return Users.instance;
}

View File

@@ -284,7 +284,7 @@ class VarState extends State {
super();
if (VarState.instance) {
// eslint-ignore-next-line no-constructor-return
// eslint-disable-next-line no-constructor-return
return VarState.instance;
}

View File

@@ -7,6 +7,7 @@
* Network interface
* @interface NetworkInterface
*/
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface NetworkInterface {
// DHCP_KEEPRESOLV="no"
// DNS_SERVER1="1.1.1.1"

View File

@@ -25,4 +25,4 @@ const client = new Docker({
/**
* Docker client
*/
export const docker = (pify(client));
export const docker = pify(client) as unknown as Promisify<Docker>;

View File

@@ -13,7 +13,7 @@ import { AppError } from '../../errors';
const data = {};
const getSubEndpoint = () => {
const httpPort = states.varState.data?.port;
const httpPort: string = states.varState.data?.port;
return `http://localhost:${httpPort}/sub`;
};
@@ -50,62 +50,64 @@ const endpointToStateMapping = {
};
const subscribe = async (endpoint: string) => {
await sleep(1000).then(async () => {
debugTimer(`subscribe(${endpoint})`);
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();
// Wait 1s before subscribing
await sleep(1000);
if (isUp) {
throw new AppError(`Cannot connect to nchan at ${getSubEndpoint()}/${endpoint}`);
}
debugTimer(`subscribe(${endpoint})`);
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();
throw new AppError('Cannot connect to nchan');
});
if (response.status === 502) {
// Status 502 is a connection timeout error,
// may happen when the connection was pending for too long,
// and the remote server or a proxy closed it
// let's reconnect
await subscribe(endpoint);
} else if (response.status === 200) {
// Get and show the message
const message = await response.text();
// Create endpoint field on data
if (!data[endpoint]) {
const fileName = endpoint + '.js';
data[endpoint] = {
handlerPath: path.resolve(__dirname, '../../states', fileName)
};
}
// Only re-run parser if the message changed
if (data[endpoint].message !== message) {
data[endpoint].updated = new Date();
data[endpoint].message = message;
try {
const state = parseConfig({
file: message,
type: 'ini'
});
// Update state
endpointToStateMapping[endpoint].parse(state);
} catch { }
}
debugTimer(`subscribe(${endpoint})`);
} else {
// An error - let's show it
coreLogger.error(JSON.stringify(response));
if (isUp) {
throw new AppError(`Cannot connect to nchan at ${getSubEndpoint()}/${endpoint}`);
}
}).then(() => {
subscribe(endpoint);
throw new AppError('Cannot connect to nchan');
});
if (response.status === 502) {
// Status 502 is a connection timeout error,
// may happen when the connection was pending for too long,
// and the remote server or a proxy closed it
// let's reconnect
await subscribe(endpoint);
} else if (response.status === 200) {
// Get and show the message
const message = await response.text();
// Create endpoint field on data
if (!data[endpoint]) {
const fileName = endpoint + '.js';
data[endpoint] = {
handlerPath: path.resolve(__dirname, '../../states', fileName)
};
}
// Only re-run parser if the message changed
if (data[endpoint].message !== message) {
data[endpoint].updated = new Date();
data[endpoint].message = message;
try {
const state = parseConfig({
file: message,
type: 'ini'
});
// Update state
endpointToStateMapping[endpoint].parse(state);
} catch { }
}
debugTimer(`subscribe(${endpoint})`);
} else {
// An error - let's show it
coreLogger.error(JSON.stringify(response));
}
// Re-subscribe
await subscribe(endpoint);
};
export const subscribeToNchanEndpoint = async (endpoint: string) => {
@@ -113,5 +115,6 @@ export const subscribeToNchanEndpoint = async (endpoint: string) => {
throw new AppError(`Invalid nchan endpoint "${endpoint}".`);
}
subscribe(endpoint);
// Subscribe
await subscribe(endpoint);
};

View File

@@ -8,8 +8,7 @@
* @param ms How many milliseconds to sleep for.
*/
export const atomicSleep = async (ms: number): Promise<any> => {
return new Promise(resolve => {
// eslint-disable-next-line no-undef
return new Promise<void>(resolve => {
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
resolve();
});

View File

@@ -60,6 +60,7 @@ const fixObjectArrays = (object: Record<string, any>) => {
// An object without any array items
const filteredObject = filterObject(object, (key, value) => {
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
const [_, name, index] = [...((key).match(/(.*):(\d+$)/) ?? [])];
if (!name || !index) {
return true;

View File

@@ -7,4 +7,10 @@
* Sleep for a certain amount of milliseconds.
* @param ms How many milliseconds to sleep for.
*/
export const sleep = async (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
export const sleep = async (ms: number) => {
return new Promise<void>(resolve => {
setTimeout(() => {
resolve();
}, ms);
});
};

View File

@@ -15,7 +15,7 @@ export const validateApiKey = async (apiKey: string) => {
const body = new FormData();
Object.entries(data).forEach(([key, value]) => {
if (value !== undefined) {
body.append(key, `${value}`);
body.append(key, String(value));
}
});

View File

@@ -25,7 +25,7 @@ const encodeParameters = (parameters: LooseObject) => {
interface Options {
/** File path */
file;
file: string;
/** HTTP Method GET/POST */
method?: string;
/** Request query */

View File

@@ -23,7 +23,7 @@ type Overload = {
/**
* Get all share types.
*/
export const getShares: Overload = (type?: any, filter?: any) => {
export const getShares: Overload = (type?: string, filter?: Filter) => {
const types = {
user: (name?: string) => processShare('user', sharesState.findOne(name ? { name } : {})),
users: () => sharesState.find().map(share => processShare('user', share)),
@@ -34,7 +34,7 @@ export const getShares: Overload = (type?: any, filter?: any) => {
// Return a type of share
if (type) {
if (!Object.keys(types).includes(type)) {
throw new AppError(`Unknown type "${type}", valid types are ${Object.keys(types)}.`);
throw new AppError(`Unknown type "${type}", valid types are ${Object.keys(types).join(', ')}.`);
}
return types[type](filter?.name);

View File

@@ -66,6 +66,7 @@ class Upcast {
function: (value: unknown) => () => value,
null: () => null,
number: (value: unknown) => Number(value),
// eslint-disable-next-line no-new-object
object: (value: unknown) => new Object(value),
string: (value: unknown) => String(value),
undefined: () => undefined

View File

@@ -1,7 +1,7 @@
import { CoreContext } from '../../../types';
import { FieldMissingError } from '../../../errors';
export const ensureData = (context: CoreContext, field) => {
export const ensureData = (context: CoreContext, field: string) => {
const hasData = context.data && Object.keys(context.data).includes(field);
if (!hasData) {

View File

@@ -1,7 +1,7 @@
import { CoreContext } from '../../../types';
import { FieldMissingError } from '../../../errors';
export const ensureParameter = (context: CoreContext, field: any) => {
export const ensureParameter = (context: CoreContext, field: string) => {
const hasParameter = context.params && Object.keys(context.params).includes(field);
if (!hasParameter) {

View File

@@ -1,7 +1,7 @@
import { CoreContext } from '../../../types';
import { FieldMissingError } from '../../../errors';
export const ensureQuery = (context: CoreContext, field: any) => {
export const ensureQuery = (context: CoreContext, field: string) => {
const hasQuery = context.query && Object.keys(context.query).includes(field);
if (!hasQuery) {

View File

@@ -24,7 +24,7 @@ export const parseDomain = async (type: DomainLookupType, id: string): Promise<D
};
if (!type || !Object.keys(types).includes(type)) {
throw new Error(`Type must be one of [${Object.keys(types)}], ${type} given.`);
throw new Error(`Type must be one of [${Object.keys(types).join(', ')}], ${type} given.`);
}
const client = await getHypervisor();

View File

@@ -3,13 +3,13 @@ import path from 'path';
import prettyBytes from 'pretty-bytes';
import { coreLogger } from '../log';
const writeFile = (filePath: string, fileContents: string | Buffer) => {
const writeFile = async (filePath: string, fileContents: string | Buffer) => {
coreLogger.debug(`Writing ${prettyBytes(fileContents.length)} to ${filePath}`);
fs.promises.writeFile(filePath, fileContents);
await fs.promises.writeFile(filePath, fileContents);
};
export const writeToBoot = (filePath: string, fileContents: string | Buffer) => {
export const writeToBoot = async (filePath: string, fileContents: string | Buffer) => {
const basePath = '/boot/config/plugins/dynamix/';
const resolvedPath = path.resolve(basePath, filePath);
writeFile(resolvedPath, fileContents);
await writeFile(resolvedPath, fileContents);
};

View File

@@ -6,6 +6,7 @@
import get from 'lodash.get';
import { v4 as uuid } from 'uuid';
import * as core from '../core';
// eslint-disable-next-line @typescript-eslint/no-duplicate-imports
import { bus, apiManager, graphqlLogger, config, pluginManager, modules, coreLogger } from '../core';
import { AppError, FatalAppError, PluginError } from '../core/errors';
import { usersState } from '../core/states';
@@ -163,10 +164,19 @@ const getPluginModule = (pluginName: string, pluginModuleName: string) => {
*/
class FuncDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field: Record<string, any>) {
// @ts-expect-error
const { args } = this;
field.resolve = async function (_source, directiveArgs: Record<string, any>, { user }, info: Record<string, any>) {
const { module: moduleName, result: resultType } = args;
field.resolve = async function (_source, directiveArgs: {
[key: string]: string | any;
plugin: string;
module: string;
result: string;
input: Record<string, any>;
query: Record<string, any>;
}, { user }, info: Record<string, any>) {
const { module: moduleName, result: resultType } = args as {
module: string;
result: string;
};
const { plugin: pluginName, module: pluginModuleName, result: pluginType, input, ...params } = directiveArgs;
const operationType = info.operation.operation;
const query = {
@@ -192,6 +202,7 @@ class FuncDirective extends SchemaDirectiveVisitor {
if (pluginName) {
// @ts-expect-error
const { filePath } = getPluginModule(pluginName, pluginModuleName);
// eslint-disable-next-line @typescript-eslint/no-var-requires
const pluginModule = require(filePath);
// The file will either use a default export or a named one
// If it's named it should be the same as a module name
@@ -203,7 +214,7 @@ class FuncDirective extends SchemaDirectiveVisitor {
if (isNodeError(error, AppError)) {
// Rethrow clean error message about module being missing
if (error.code === 'MODULE_NOT_FOUND') {
throw new AppError(`Cannot find ${pluginName ? 'Plugin: "' + pluginName + '" ' : ''}Module: "${pluginName ? pluginModuleName : moduleName}"`);
throw new AppError(`Cannot find ${pluginName ? `Plugin: "${pluginName}" ` : ''}Module: "${pluginName ? pluginModuleName : moduleName}"`);
}
}
@@ -316,7 +327,7 @@ bus.on('varstate', async data => {
const user = usersState.findOne({ name: 'root' });
if (user) {
publish('info', 'UPDATED', {
await publish('info', 'UPDATED', {
os: {
hostname
}
@@ -338,7 +349,7 @@ dee.on('*', async (data: { Type: string }) => {
const { json } = await modules.getAppCount({
user
});
publish('info', 'UPDATED', {
await publish('info', 'UPDATED', {
apps: json
});
}
@@ -391,7 +402,7 @@ export const graphql = {
resolve({
user,
websocketId
}); return;
});
} catch (error: unknown) {
reject(error);
}
@@ -416,7 +427,12 @@ export const graphql = {
return;
}
const { user, websocketId } = context;
const { user, websocketId } = context as {
user: {
name: string;
};
websocketId: string;
};
graphqlLogger.debug(`<ws> ${user.name}[${websocketId}] disconnected.`);
// Update ws connection count and other needed values

View File

@@ -64,7 +64,10 @@ export const Subscription = {
...createSubscription('vms/domains')
},
pluginModule: {
subscribe: async (_: unknown, directiveArgs, context: Context) => {
subscribe: async (_: unknown, directiveArgs: {
plugin: string;
module: string;
}, context: Context) => {
const { plugin: pluginName, module: pluginModuleName } = directiveArgs;
const channel = `${pluginName}/${pluginModuleName}`;

View File

@@ -31,7 +31,7 @@ export const createSubscription = (channel: string, resource?: string) => ({
// Check the user has permissison to subscribe to this endpoint
ensurePermission(context.user, {
resource: resource || channel,
resource: resource ?? channel,
action: 'read',
possession: 'any'
});
@@ -83,8 +83,9 @@ export const getServers = async (): Promise<Server[]> => {
const guid = varState?.data?.regGuid;
const name = varState?.data?.name;
const wanip = null;
const lanip = networkState.data[0].ipaddr[0];
const localurl = `http://${lanip}:${varState?.data?.port}`;
const lanip: string = networkState.data[0].ipaddr[0];
const port: string = varState?.data?.port;
const localurl = `http://${lanip}:${port}`;
const remoteurl = null;
return [{

View File

@@ -75,21 +75,24 @@ export class CustomSocket {
}
public onConnect() {
const logger = this.logger;
const connection = this.connection;
const apiKey = this.apiKey;
// eslint-disable-next-line @typescript-eslint/no-this-alias
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);
logger.debug('Connected via %s.', connection?.url);
// Reset connection attempts
customSocket.connectionAttempts = 0;
} catch (error: unknown) {
if (isNodeError(error, AppError)) {
this.close(Number(error.code?.length === 4 ? error.code : `4${error.code}`), JSON.stringify({
this.close(Number(error.code?.length === 4 ? error.code : `4${error.code ?? 500}`), JSON.stringify({
message: error.message ?? 'Internal Server Error'
}));
}
@@ -97,24 +100,86 @@ export class CustomSocket {
};
}
public onMessage() {
const logger = this.logger;
return async function (message: string, ...args: any[]) {
logger.silly('message="%s" args="%s"', message, ...args);
};
}
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();
// Check the connection is allowed
await this.isConnectionAllowed();
// Cleanup old connections
await this.cleanup();
// Connect to endpoint
await this._connect();
// Log we connected
this.logger.debug('Connected to %s', this.uri);
} catch (error: unknown) {
if (isNodeError(error, AppError)) {
this.logger.error('Failed connecting reason=%s', error.message);
}
} finally {
lock.release();
}
}
public async disconnect() {
const lock = await this.getLock();
try {
if (this.connection && (this.connection.readyState !== this.connection.CLOSED)) {
// 4200 === ok
this.connection.close(4200);
}
} catch (error: unknown) {
if (isNodeError(error, AppError)) {
this.logger.error('Failed disconnecting reason=%s', error.message);
}
} finally {
lock.release();
}
}
public async reconnect() {
await this.disconnect();
await sleep(1000);
await this.connect();
}
protected onDisconnect() {
const logger = this.logger;
const connect = this.connect.bind(this);
const connectionAttempts = this.connectionAttempts;
const uri = this.uri;
const responses = {
// OK
4200: async () => {
// This is usually because the API key is updated
// Let's reset the reconnect count so we reconnect instantly
customSocket.connectionAttempts = 0;
this.connectionAttempts = 0;
},
// Unauthorized - Invalid/missing API key.
4401: async () => {
customSocket.logger.debug('Invalid API key, waiting for new key...');
this.logger.debug('Invalid API key, waiting for new key...');
},
// Rate limited
4429: async message => {
try {
let interval: NodeJS.Timeout | undefined;
const retryAfter = parseInt(message['Retry-After'], 10) || 30;
customSocket.logger.debug('Rate limited, retrying after %ss', retryAfter);
this.logger.debug('Rate limited, retrying after %ss', retryAfter);
// Less than 30s
if (retryAfter <= 30) {
@@ -123,7 +188,7 @@ export class CustomSocket {
// Print retry once per second
interval = setInterval(() => {
seconds--;
customSocket.logger.debug('Retrying connection in %ss', seconds);
this.logger.debug('Retrying connection in %ss', seconds);
}, ONE_SECOND);
}
@@ -143,11 +208,10 @@ export class CustomSocket {
await sleep(ONE_SECOND * 5);
}
};
const customSocket = this;
return async function (this: WebSocketWithHeartBeat, code: number, _message: string) {
try {
const message: { message?: string } = _message.trim() === '' ? { message: '' } : JSON.parse(_message);
customSocket.logger.debug('Connection closed with code=%s reason="%s"', code, code === 1006 ? 'Terminated' : message.message);
logger.debug('Connection closed with code=%s reason="%s"', code, code === 1006 ? 'Terminated' : message.message);
// Stop ws heartbeat
if (this.heartbeat) {
@@ -163,31 +227,24 @@ export class CustomSocket {
}
} catch (error: unknown) {
if (isNodeError(error, AppError)) {
customSocket.logger.debug('Connection closed with code=%s reason="%s"', code, error.message);
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));
await sleep(backoff(connectionAttempts, ONE_MINUTE, 5));
// Reconnect
await customSocket.connect(customSocket.connectionAttempts + 1);
await connect(connectionAttempts + 1);
} catch (error: unknown) {
if (isNodeError(error, AppError)) {
customSocket.logger.debug('Failed reconnecting to %s reason="%s"', customSocket.uri, error.message);
logger.debug('Failed reconnecting to %s reason="%s"', uri, error.message);
}
}
};
}
public onMessage() {
const customSocket = this;
return async function (message: string, ...args: any[]) {
customSocket.logger.silly('message="%s" args="%s"', message, ...args);
};
}
protected async cleanup() {
// Kill existing socket connection
if (this.connection) {
@@ -263,61 +320,5 @@ export class CustomSocket {
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('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 = 0) {
const lock = await this.getLock();
try {
// Set retry attempt count
await this.setRetryAttempt(retryAttempt);
// Get the current apiKey
this.apiKey = await this.getApiKey();
// Check the connection is allowed
await this.isConnectionAllowed();
// Cleanup old connections
await this.cleanup();
// Connect to endpoint
await this._connect();
// Log we connected
this.logger.debug('Connected to %s', this.uri);
} catch (error: unknown) {
if (isNodeError(error, AppError)) {
this.logger.error('Failed connecting reason=%s', error.message);
}
} finally {
lock.release();
}
}
public async disconnect() {
const lock = await this.getLock();
try {
if (this.connection && (this.connection.readyState !== this.connection.CLOSED)) {
// 4200 === ok
this.connection.close(4200);
}
} catch (error: unknown) {
if (isNodeError(error, AppError)) {
this.logger.error('Failed disconnecting reason=%s', error.message);
}
} finally {
lock.release();
}
}
public async reconnect() {
await this.disconnect();
await sleep(1000);
await this.connect();
}
}

View File

@@ -53,7 +53,7 @@ export class InternalGraphql extends CustomSocket {
await sleep(1000);
// Re-connect to internal graphql server
connect();
await connect();
return;
}

View File

@@ -34,7 +34,7 @@ export const subscribeToServers = async (apiKey: string) => {
// Subscribe
const subscription = query.subscribe({
next: ({ data, errors }) => {
next: async ({ 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);
@@ -54,7 +54,7 @@ export const subscribeToServers = async (apiKey: string) => {
});
// Update subscribers
pubsub.publish('servers', {
await pubsub.publish('servers', {
servers: data.servers
});
}

View File

@@ -1,4 +1,4 @@
import type { CoreContext, CoreResult } from './core/types';
import type { CoreContext, CoreResult, Result } from './core/types';
import { pubsub, coreLogger } from './core';
import { debugTimer, isNodeError } from './core/utils';
import { AppError } from './core/errors';
@@ -27,7 +27,7 @@ export const publish = async (channel: string, mutation: string, node?: Record<s
interface RunOptions {
node?: Record<string, unknown>;
moduleToRun?: (context: CoreContext) => Promise<CoreResult>;
moduleToRun?: (context: CoreContext) => Promise<CoreResult | Result> | CoreResult | Result;
context?: any;
}

3225
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,9 +12,9 @@
"copy-schemas": "cpx app/**/*.graphql dist/types",
"clean": "modclean --no-progress --run --path .",
"commit": "npx git-cz",
"lint": "xo --verbose",
"lint:quiet": "xo --quiet",
"lint:fix": "xo --fix",
"lint": "eslint app/**/*.ts",
"lint:quiet": "eslint --quiet",
"lint:fix": "eslint --fix",
"test": "npx nyc@latest ava",
"cover": "npm run cover:unit && npm run cover:integration && npm run cover:report",
"cover:unit": "nyc --silent npm run test:unit",
@@ -124,7 +124,10 @@
"devDependencies": {
"@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^11.0.0",
"@types/cli-table": "^0.3.0",
"@types/dockerode": "^3.2.2",
"@types/lodash.get": "^4.4.6",
"@types/pify": "^5.0.0",
"@types/semver-regex": "^3.1.0",
"@types/stoppable": "^1.1.0",
"@types/supertest": "^2.0.10",
@@ -135,6 +138,7 @@
"cpx": "1.5.0",
"cz-conventional-changelog": "3.3.0",
"eslint": "^7.18.0",
"esprint": "^2.0.0",
"husky": "4.3.8",
"modclean": "^3.0.0-beta.1",
"node-env-run": "^4.0.2",
@@ -143,9 +147,9 @@
"standard-version": "^9.1.0",
"supertest": "^6.0.1",
"ts-node": "9.1.1",
"tsup": "^3.12.0",
"typescript": "4.1.3",
"typescript-coverage-report": "^0.4.0",
"xo": "0.37.1"
"typescript-coverage-report": "^0.4.0"
},
"optionalDependencies": {
"libvirt": "^1.2.1"
@@ -160,11 +164,6 @@
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"xo": {
"rules": {
"max-params": 0
}
},
"bundledDependencies": [
"@apollo/client",
"@graphql-tools/load-files",

View File

@@ -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. */
}
}