From 15a61f6932f4b1ae2d4c003e4c5488491b40a33b Mon Sep 17 00:00:00 2001 From: Karl-Philipp Wulfert Date: Tue, 28 May 2019 15:36:20 +0200 Subject: [PATCH] refactor: adjust code to updated dependencies --- src/common.ts | 24 +++-- src/{Logger.ts => logger.ts} | 87 ++++++++++------ src/{SMTP.ts => smtp.ts} | 162 ++++++++++++++++++----------- src/{Transport.ts => transport.ts} | 2 +- 4 files changed, 175 insertions(+), 100 deletions(-) rename src/{Logger.ts => logger.ts} (66%) rename src/{SMTP.ts => smtp.ts} (74%) rename src/{Transport.ts => transport.ts} (97%) diff --git a/src/common.ts b/src/common.ts index 577f9a7c..130e07f4 100644 --- a/src/common.ts +++ b/src/common.ts @@ -18,7 +18,7 @@ * * Copied from https://stackoverflow.com/a/51365037 */ -import {Transport, VerifiableTransport} from './Transport'; +import {Transport, VerifiableTransport} from './transport'; export type RecursivePartial = { [P in keyof T]?: T[P] extends Array ? @@ -28,24 +28,32 @@ export type RecursivePartial = { /** * Deletes all properties that are undefined from an object - * @param obj + * + * @param obj Object to delete undefined properties from */ -export function deleteUndefinedProperties(obj: any) { +export function deleteUndefinedProperties(obj: unknown) { // return atomic data types and arrays (recursion anchor) if (typeof obj !== 'object' || Array.isArray(obj)) { return obj; } // check each key - Object.keys(obj).forEach((key) => { - if (typeof obj[key] === 'undefined') { + for (const key in obj) { + /* istanbul ignore if */ + if (!obj.hasOwnProperty(key)) { + continue; + } + + const indexedObj = obj as { [k: string]: unknown; }; + + if (typeof indexedObj[key] === 'undefined') { // delete undefined keys - delete obj[key]; + delete indexedObj[key]; } else { // check recursive - obj[key] = deleteUndefinedProperties(obj[key]); + indexedObj[key] = deleteUndefinedProperties(indexedObj[key]); } - }); + } return obj; } diff --git a/src/Logger.ts b/src/logger.ts similarity index 66% rename from src/Logger.ts rename to src/logger.ts index 178e219f..a6d7a605 100644 --- a/src/Logger.ts +++ b/src/logger.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, 2019 StApps + * Copyright (C) 2019 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. @@ -15,7 +15,17 @@ import chalk from 'chalk'; import {stringify} from 'flatted'; import {isNodeEnvironment, isProductiveNodeEnvironment} from './common'; -import {Transport} from './Transport'; +import {Transport} from './transport'; + +/** + * Check if something has property STAPPS_LOG_LEVEL + * + * @param something Something to check + */ +// tslint:disable-next-line:completed-docs +function hasStAppsLogLevel(something: object): something is { STAPPS_LOG_LEVEL: number; } { + return 'STAPPS_LOG_LEVEL' in something; +} /** * Logger with colors, loglevel and transport @@ -33,10 +43,15 @@ import {Transport} from './Transport'; * ``` */ export class Logger { + /** + * Base of binary system + */ + private static readonly binaryBase = 2; + /** * Log levels */ - private static logLevels = [ + private static readonly logLevels = [ 'INFO', 'LOG', 'WARN', @@ -44,6 +59,11 @@ export class Logger { 'OK', ]; + /** + * Log level sum, equivalent to all log levels enabled + */ + private static readonly logLevelSum = Math.pow(Logger.binaryBase, Logger.logLevels.length) - 1; + /** * Transport for errors */ @@ -52,13 +72,13 @@ export class Logger { /** * Check if intended log level is allowed in environment log level * - * @param logLevel + * @param logLevel Log level to check */ private static checkLogLevel(logLevel: string): boolean { - const logLevelNumber = Math.pow(2, Logger.logLevels.indexOf(logLevel) + 1) - 1; + const logLevelNumber = Math.pow(Logger.binaryBase, Logger.logLevels.indexOf(logLevel) + 1) - 1; - /* tslint:disable-next-line:no-bitwise */ - return !!(Logger.getLogLevel() & logLevelNumber); + // tslint:disable-next-line:no-bitwise + return (Logger.getLogLevel() & logLevelNumber) > 0; } /** @@ -68,13 +88,15 @@ export class Logger { if (isNodeEnvironment() && typeof process.env.STAPPS_LOG_LEVEL !== 'undefined') { // Node.js environment exists return parseInt(process.env.STAPPS_LOG_LEVEL, 10); - } else if (typeof window !== 'undefined' && typeof (window as any).STAPPS_LOG_LEVEL === 'number') { + } + + if (typeof window !== 'undefined' && hasStAppsLogLevel(window)) { // browser environment exists - return (window as any).STAPPS_LOG_LEVEL; + return window.STAPPS_LOG_LEVEL; } // Log everything - return 31; + return Logger.logLevelSum; } /** @@ -82,7 +104,7 @@ export class Logger { * * @param args Arguments to log */ - public static async error(...args: any[]): Promise { + public static async error(...args: unknown[]): Promise { if (!Logger.checkLogLevel('ERROR')) { return; } @@ -93,7 +115,9 @@ export class Logger { if (isProductiveNodeEnvironment()) { if (typeof Logger.transport !== 'undefined') { return Logger.transport.send('Error', Logger.stringifyArguments(...args)); - } else if (!process.env.ALLOW_NO_TRANSPORT) { + } + + if (process.env.ALLOW_NO_TRANSPORT !== 'true') { throw new Error(`Error couldn't be transported! Please set a transport or set ALLOW_NO_TRANSPORT='true'.`); } } @@ -104,13 +128,13 @@ export class Logger { * * @param args Arguments to log */ - public static info(...args: any[]): void { + public static info(...args: unknown[]): void { if (!Logger.checkLogLevel('INFO')) { return; } /* tslint:disable-next-line:no-console */ - console.info(chalk.cyan(`[INFO] ${Logger.stringifyArguments(args)}`)); + console.info(chalk.cyan(`[INFO] ${Logger.stringifyArguments(...args)}`)); } /** @@ -118,12 +142,12 @@ export class Logger { */ public static initialized(): void { if (isProductiveNodeEnvironment() && typeof Logger.transport === 'undefined') { - if (!process.env.ALLOW_NO_TRANSPORT) { + if (process.env.ALLOW_NO_TRANSPORT !== 'true') { throw new Error(`Productive environment doesn't set a transport for error notifications.`); - } else { - /* tslint:disable-next-line:no-console */ - console.warn(chalk.yellow(`Productive environment doesn't set a transport for error notifications.`)); } + + /* tslint:disable-next-line:no-console */ + console.warn(chalk.yellow(`Productive environment doesn't set a transport for error notifications.`)); } } @@ -132,13 +156,13 @@ export class Logger { * * @param args Arguments to log */ - public static log(...args: any[]): void { - if (!this.checkLogLevel('LOG')) { + public static log(...args: unknown[]): void { + if (!Logger.checkLogLevel('LOG')) { return; } /* tslint:disable-next-line:no-console */ - console.log(chalk.white(`[LOG] ${Logger.stringifyArguments(args)}`)); + console.log(chalk.white(`[LOG] ${Logger.stringifyArguments(...args)}`)); } /** @@ -146,13 +170,13 @@ export class Logger { * * @param args Arguments to log */ - public static ok(...args: any[]): void { - if (!this.checkLogLevel('OK')) { + public static ok(...args: unknown[]): void { + if (!Logger.checkLogLevel('OK')) { return; } /* tslint:disable-next-line:no-console */ - console.log(chalk.bold.green(`[OK] ${Logger.stringifyArguments(args)}`)); + console.log(chalk.bold.green(`[OK] ${Logger.stringifyArguments(...args)}`)); } /** @@ -169,20 +193,19 @@ export class Logger { * * @param args Arguments to stringify */ - public static stringifyArguments(...args: any[]): string { + public static stringifyArguments(...args: unknown[]): string { const result: string[] = []; args.forEach((argument) => { - const type = typeof argument; - - if (['string', 'number'].indexOf(type) !== -1) { - result.push(argument); + if (typeof argument === 'string' || typeof argument === 'number') { + result.push(argument.toString()); } else if (argument instanceof Error) { result.push(argument.message); if (typeof argument.stack !== 'undefined') { result.push(argument.stack); } } else { + // tslint:disable-next-line:no-magic-numbers result.push(stringify(argument, null, 2)); } }); @@ -195,12 +218,12 @@ export class Logger { * * @param args Arguments to log */ - public static warn(...args: any[]): void { - if (!this.checkLogLevel('WARN')) { + public static warn(...args: unknown[]): void { + if (!Logger.checkLogLevel('WARN')) { return; } /* tslint:disable-next-line:no-console */ - console.warn(chalk.yellow(`[WARN] ${Logger.stringifyArguments(args)}`)); + console.warn(chalk.yellow(`[WARN] ${Logger.stringifyArguments(...args)}`)); } } diff --git a/src/SMTP.ts b/src/smtp.ts similarity index 74% rename from src/SMTP.ts rename to src/smtp.ts index 681a5c8f..c67d1660 100644 --- a/src/SMTP.ts +++ b/src/smtp.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, 2019 StApps + * Copyright (C) 2019 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. @@ -15,23 +15,56 @@ import * as nodemailer from 'nodemailer'; import {MailOptions} from 'nodemailer/lib/sendmail-transport'; import {deleteUndefinedProperties, isProductiveEnvironment, RecursivePartial} from './common'; -import {VerifiableTransport} from './Transport'; +import {VerifiableTransport} from './transport'; /** * A configuration of the transport used to send mails via SMTP */ export interface SMTPConfig { + /** + * Auth configuration + */ auth: { + /** + * Password for login + */ password: string; + /** + * User for login + */ user: string; }; + /** + * List of "carbon copy" recipients + */ cc?: string[]; + /** + * SMTP host server + */ host: string; + /** + * SMTP port + */ port: number; + /** + * List of recipients + */ recipients: string[]; + /** + * Whether or not to establish a secure connection + */ secure?: boolean; + /** + * Sender configuration + */ sender: { + /** + * Mail of sender + */ mail: string; + /** + * Name of sender + */ name?: string; }; } @@ -40,7 +73,9 @@ export interface SMTPConfig { * An implementation of mail transport via SMTP */ export class SMTP extends VerifiableTransport { - + /** + * Singleton instance + */ private static _instance: SMTP; /** @@ -51,8 +86,14 @@ export class SMTP extends VerifiableTransport { /** * Who is using this service */ - private from: { + private readonly from: { + /** + * Mail of sender + */ mail: string; + /** + * Name of sender + */ name?: string; }; @@ -64,7 +105,7 @@ export class SMTP extends VerifiableTransport { /** * Connection to SMTP server */ - private transportAgent: nodemailer.Transporter; + private readonly transportAgent: nodemailer.Transporter; /** * Set to true if the transport was verified @@ -84,30 +125,31 @@ export class SMTP extends VerifiableTransport { * SMTP_SENDER_MAIL: sender of the mail * SMTP_SENDER_NAME: name of the sender * SMTP_SECURE: `true` to enable tls - * @param config {SMTPConfig} - * @return {Transport} + * + * @param config SMTP config for instance */ public static getInstance(config?: SMTPConfig): SMTP | undefined { // if an instance of SMTP already exists - if (this._instance) { - return this._instance; + if (typeof SMTP._instance !== 'undefined') { + return SMTP._instance; } // monitoring is not required -> SMTP init can fail if (!isProductiveEnvironment() || process.env.ALLOW_NO_TRANSPORT === 'true') { try { - this._instance = new this(config); + SMTP._instance = new SMTP(config); } catch (err) { /* tslint:disable-next-line:no-console */ console.warn('SMTP config failed.'); + return; } } else { // monitoring is required -> SMTP will throw error if config is invalid - this._instance = new this(config); + SMTP._instance = new SMTP(config); } - return this._instance; + return SMTP._instance; } /** @@ -117,8 +159,7 @@ export class SMTP extends VerifiableTransport { * For more information please consider reading * https://stackoverflow.com/a/2071250 * - * @param {string} address - * @return {boolean} + * @param address Address to validate */ public static isValidEmailAddress(address: string): boolean { // tslint:disable-next-line:max-line-length @@ -126,20 +167,22 @@ export class SMTP extends VerifiableTransport { } /** - * Checks a list of mail addresses for validity. - * @param {string[]} recipients - * @return {string[]} + * Checks a list of mail addresses for validity + * + * @param recipients List of recipients to check */ public static isValidRecipientsList(recipients: string[] | undefined): boolean { - return Array.isArray(recipients) && recipients.length > 0 && recipients.every(this.isValidEmailAddress); + return Array.isArray(recipients) && recipients.length > 0 && recipients.every(SMTP.isValidEmailAddress); } /** - * Creates an SMTP connection. WARNING: This class is supposed to be used as a singleton. You should never - * call `new SMTP()` - * @param {SMTPConfig} config + * Creates an SMTP connection. + * + * WARNING: This class is supposed to be used as a singleton. You should never call `new SMTP()` + * + * @param smtpConfig SMTP config */ - private constructor(config?: SMTPConfig) { + private constructor(smtpConfig?: SMTPConfig) { // create a partial config from environment variables that can overwrite the given config const envConfig: RecursivePartial = { auth: { @@ -159,52 +202,51 @@ export class SMTP extends VerifiableTransport { }, }; - // @ts-ignore - config = { - ...config, + const config = { + ...smtpConfig, // deleting undefined properties so the actual config doesn't get overwritten by undefined values ...deleteUndefinedProperties(envConfig), - }; + } as SMTPConfig; - if (typeof config!.host === 'undefined') { + if (typeof config.host === 'undefined') { throw new Error( 'SMTP configuration needs a host. Add it to the config or use environment variables (SMTP_HOST).', ); } - if (typeof config!.port === 'undefined' || isNaN(config!.port)) { + if (typeof config.port === 'undefined' || isNaN(config.port)) { throw new Error( 'SMTP configuration needs a port. Add it to the config or use environment variables (SMTP_PORT).', ); } - if (typeof config!.auth !== 'object') { + if (typeof config.auth !== 'object') { throw new Error( 'SMTP configuration needs an auth object.' + 'Add it to the config or use environment variables (SMTP_AUTH_USER, SMTP_AUTH_PASSWORD).', ); } - if (typeof config!.auth.user === 'undefined') { + if (typeof config.auth.user === 'undefined') { throw new Error( 'SMTP auth configuration needs a user. Add it to the config or use environment variables (SMTP_AUTH_USER).', ); } - if (typeof config!.auth.password === 'undefined') { + if (typeof config.auth.password === 'undefined') { throw new Error( 'SMTP auth configuration needs a password.' + 'Add it to the config or use environment variables (SMTP_AUTH_PASSWORD).', ); } - if (Array.isArray(config!.recipients) && config!.recipients.length < 1) { + if (Array.isArray(config.recipients) && config.recipients.length < 1) { throw new Error( 'SMTP configuration needs recipients. Add it to the config or use environment variables (SMTP_RECIPIENTS).', ); } - if (typeof config!.sender.mail === 'undefined') { + if (typeof config.sender.mail === 'undefined') { throw new Error( 'SMTP configuration needs a sender. Add it to the config or use environment variables (SMTP_SENDER_MAIL).', ); @@ -212,15 +254,15 @@ export class SMTP extends VerifiableTransport { super(); - if (SMTP.isValidRecipientsList(config!.recipients)) { - this.recipients = config!.recipients; + if (SMTP.isValidRecipientsList(config.recipients)) { + this.recipients = config.recipients; } else { throw new Error('Invalid recipients found'); } - if (typeof config!.cc !== 'undefined') { - if (SMTP.isValidRecipientsList(config!.cc)) { - this.cc = config!.cc!; + if (typeof config.cc !== 'undefined') { + if (SMTP.isValidRecipientsList(config.cc)) { + this.cc = config.cc; } else { throw new Error('Invalid cc recipients found'); } @@ -228,25 +270,24 @@ export class SMTP extends VerifiableTransport { this.cc = []; } - this.from = config!.sender; + this.from = config.sender; this.verified = false; // creating transport with configuration this.transportAgent = nodemailer.createTransport({ auth: { - pass: config!.auth.password, - user: config!.auth.user, + pass: config.auth.password, + user: config.auth.user, }, - host: config!.host, - port: config!.port, - secure: typeof config!.secure !== 'undefined' ? config!.secure : false, + host: config.host, + port: config.port, + secure: typeof config.secure !== 'undefined' ? config.secure : false, }); } /** * Check if instance was verified at least once - * @returns {boolean} */ public isVerified(): boolean { return this.verified; @@ -255,11 +296,12 @@ export class SMTP extends VerifiableTransport { /** * Sends a preconfigured mail with recipients and sender configured on * creation of the class (set by environment or on creation of this class) - * @param {string} subject - * @param {string} message + * + * @param subject Subject of the mail + * @param message message of the mail */ public async send(subject: string, message: string): Promise { - return await this.sendMail({ + return this.sendMail({ cc: this.cc, // use an address block if name is available, mail otherwise from: (typeof this.from.name !== 'string') ? `${this.from.name} <${this.from.mail}>` : this.from.mail, @@ -271,7 +313,8 @@ export class SMTP extends VerifiableTransport { /** * Sends a mail object - * @param {MailOptions} mail + * + * @param mail Mail to send */ public async sendMail(mail: MailOptions): Promise { // info is the response of the smtp server @@ -299,20 +342,20 @@ export class SMTP extends VerifiableTransport { */ public async verify(): Promise { - let verificationSuccessfull: boolean = false; + let verificationSuccessfull = false; try { verificationSuccessfull = await this.transportAgent.verify(); } catch (err) { if (!isProductiveEnvironment() || process.env.ALLOW_NO_TRANSPORT !== 'true') { throw err; - } else { - /* tslint:disable-next-line:no-console */ - console.warn( - 'SMTP verification error was ignored, because tranport failures are allowed: ', - err.message, - ); } + + /* tslint:disable-next-line:no-console */ + console.warn( + 'SMTP verification error was ignored, because tranport failures are allowed: ', + err.message, + ); } if (!verificationSuccessfull) { @@ -322,12 +365,13 @@ export class SMTP extends VerifiableTransport { 'If you want to ignore this error set' + '`NODE_ENV=dev` or `ALLOW_NO_TRANSPORT=true`', ); - } else { - /* tslint:disable-next-line:no-console */ - console.warn('SMTP verification error was ignored, because tranport failures are allowed.'); } + + /* tslint:disable-next-line:no-console */ + console.warn('SMTP verification error was ignored, because tranport failures are allowed.'); } this.verified = verificationSuccessfull; + return verificationSuccessfull; } } diff --git a/src/Transport.ts b/src/transport.ts similarity index 97% rename from src/Transport.ts rename to src/transport.ts index 7a2ea33b..3b5a654e 100644 --- a/src/Transport.ts +++ b/src/transport.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, 2019 StApps + * Copyright (C) 2019 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3.