diff --git a/src/command/baseSubCommand.js b/src/command/baseSubCommand.js index d060cae..20752af 100644 --- a/src/command/baseSubCommand.js +++ b/src/command/baseSubCommand.js @@ -1,10 +1,7 @@ -/** - * @file base sub command - * @author atom-yang - */ import { interopImportCJSDefault } from 'node-cjs-interop'; import asyncValidator from 'async-validator'; const Schema = interopImportCJSDefault(asyncValidator); + import inquirer from 'inquirer'; import ora from 'ora'; import { logger } from '../utils/myLogger.js'; @@ -16,17 +13,25 @@ import { globalOptionsPrompts, strictGlobalOptionValidatorDesc } from '../utils/ const defaultOraOptions = { text: 'AElf loading...' }; +/** + * @typedef {import('commander').Command} Command + * @typedef {import('ora').Options} OraOptions + * @typedef {import('../../types/rc/index.js').default} Registry + */ +/** + * @class + */ class BaseSubCommand { /** * @param {string} commandName sub command name - * @param {Object[]} parameters sub command parameters + * @param {{ [key: string]: any }[]} parameters sub command parameters * @param {string} description sub command description - * @param {Object[]} options sub command options + * @param {{ [key: string]: any }[]} options sub command options * @param {string[]} usage make examples * @param {Registry} rc instance of Registry - * @param {Object} validatorDesc rules of async-validator - * @param {Object} oraOptions an ora options + * @param {{ [key: string]: any }} validatorDesc rules of async-validator + * @param {{ [key: string]: any }} oraOptions an ora options */ constructor( commandName, @@ -54,11 +59,17 @@ class BaseSubCommand { }; }); } - + /** + * Sets custom prompts. + * @param {any} val - The value to set for custom prompts. + */ setCustomPrompts(val) { this.customPrompts = val; } - + /** + * Initializes the sub command with commander. + * @param {Command} commander - The commander instance. + */ init(commander) { let command = commander.command(`${this.commandName} ${this.getParameters()}`).description(this.description); @@ -79,6 +90,10 @@ class BaseSubCommand { }); } + /** + * Retrieves parameters as a string. + * @returns {string} Parameters string. + */ getParameters() { return this.parameters .map(v => { @@ -89,12 +104,23 @@ class BaseSubCommand { .join(' '); } + /** + * Handles errors related to universal options. + * @param {any} error - The error to handle. + */ handleUniOptionsError(error) { const { errors = [] } = error; + // @ts-ignore logger.error(errors.reduce((acc, i) => `${acc}${i.message}\n`, '')); process.exit(1); } + /** + * Retrieves universal configuration. + * @static + * @param {Command} commander - The commander instance. + * @returns {Record} Universal configuration. + */ static getUniConfig(commander) { const result = {}; ['password', 'endpoint', 'account', 'datadir'].forEach(v => { @@ -106,6 +132,12 @@ class BaseSubCommand { return result; } + /** + * Parses a boolean value. + * @static + * @param {any} val - The value to parse. + * @returns {any} Parsed boolean value. + */ static parseBoolean(val) { if (val === 'true') { return true; @@ -116,6 +148,12 @@ class BaseSubCommand { return val; } + /** + * Normalizes configuration object. + * @static + * @param {any} obj - The configuration object to normalize. + * @returns {Record} Normalized configuration object. + */ static normalizeConfig(obj) { // dash to camel-case // 'true', 'false' to true, false @@ -128,7 +166,16 @@ class BaseSubCommand { }); return result; } - + /** + * Runs the sub command. + * @param {Command} commander - The commander instance. + * @param {...any} args - Additional arguments. + * @returns {Promise<{ + * localOptions: { [key: string]: any }, + * options: { [key: string]: any }, + * subOptions: { [key: string]: any } + * } | void>} Promise resolving to options or void. + */ async run(commander, ...args) { let subCommandOptions = {}; args.slice(0, this.parameters.length).forEach((v, i) => { @@ -188,7 +235,10 @@ class BaseSubCommand { subOptions: subCommandOptions }; } - + /** + * Generates examples for usage. + * @returns {string[]} Array of example strings. + */ makeExamples() { return this.usage.map(cmd => `aelf-command ${this.commandName} ${cmd}`); } diff --git a/src/command/call.js b/src/command/call.js index 830cdd2..3779bdb 100644 --- a/src/command/call.js +++ b/src/command/call.js @@ -1,7 +1,3 @@ -/** - * @file call read-only method on contract - * @author atom-yang - */ import AElf from 'aelf-sdk'; import inquirer from 'inquirer'; import chalk from 'chalk'; @@ -18,7 +14,21 @@ import { import { getWallet } from '../utils/wallet.js'; import { logger } from '../utils/myLogger.js'; +/** + * @typedef {import('commander').Command} Command + * @typedef {import('ora').Options} OraOptions + * @typedef {import('../../types/rc/index.js').default} Registry + */ class CallCommand extends BaseSubCommand { + /** + * Creates an instance of CallCommand. + * @param {Registry} rc The instance of the Registry. + * @param {string} [name] The name of the command. + * @param {string} [description] The description of the command. + * @param {Object[]} [parameters] The parameters for the command. + * @param {string[]} [usage] The usage examples for the command. + * @param {any[]} [options] The options for the command. + */ constructor( rc, name = 'call', @@ -29,22 +39,40 @@ class CallCommand extends BaseSubCommand { ) { super(name, parameters, description, options, usage, rc); } - + /** + * Calls a method with specified parameters. + * @param {any} method The method to call. + * @param {any} params The parameters for the method call. + * @returns {Promise} A promise that resolves with the result of the method call. + */ async callMethod(method, params) { this.oraInstance.start('Calling method...'); const result = await method.call(params); this.oraInstance.succeed('Calling method successfully!'); return result; } - + /** + * Processes address after prompting for input. + * @param {any} aelf The AElf instance. + * @param {any} wallet The wallet instance. + * @param {Object.} answerInput The input parameters. + * @returns {Promise} A promise that resolves with the processed result. + */ async processAddressAfterPrompt(aelf, wallet, answerInput) { let { contractAddress } = BaseSubCommand.normalizeConfig(answerInput); contractAddress = await getContractInstance(contractAddress, aelf, wallet, this.oraInstance); return contractAddress; } + /** + * Runs the command. + * @param {Command} commander The Commander instance. + * @param {...any[]} args Additional arguments passed to the command. + * @returns {Promise} A promise that resolves when the command execution completes. + */ async run(commander, ...args) { this.setCustomPrompts(true); + // @ts-ignore const { options, subOptions } = await super.run(commander, ...args); const subOptionsLength = Object.keys(subOptions).length; const { endpoint, datadir, account, password } = options; @@ -102,10 +130,12 @@ class CallCommand extends BaseSubCommand { params = ''; } const result = await this.callMethod(method, params); + // @ts-ignore logger.info(`\nResult:\n${JSON.stringify(result, null, 2)}`); this.oraInstance.succeed('Succeed!'); } catch (e) { this.oraInstance.fail('Failed!'); + // @ts-ignore logger.fatal(e); } } diff --git a/src/command/config.js b/src/command/config.js index 08d94d4..b002d7d 100644 --- a/src/command/config.js +++ b/src/command/config.js @@ -1,7 +1,3 @@ -/** - * @file get block height - * @author atom-yang - */ import { interopImportCJSDefault } from 'node-cjs-interop'; import asyncValidator from 'async-validator'; const Schema = interopImportCJSDefault(asyncValidator); @@ -17,7 +13,17 @@ const configCommandValidatorDesc = { } }; +/** + * @typedef {import('commander').Command} Command + * @typedef {import('async-validator').Rules} Rules + * @typedef {import('async-validator').Values} Values + * @typedef {import('../../types/rc/index.js').default} Registry + */ class ConfigCommand extends BaseSubCommand { + /** + * Constructs a new ConfigCommand instance. + * @param {Registry} rc - The registry instance. + */ constructor(rc) { super( 'config', @@ -29,7 +35,12 @@ class ConfigCommand extends BaseSubCommand { configCommandValidatorDesc ); } - + /** + * Validates parameters based on the provided rules. + * @param {Rules} rule - The validation rules. + * @param {Values} parameters - The parameters to validate. + * @returns {Promise} A promise that resolves when the validation is complete. + */ async validateParameters(rule, parameters) { const validator = new Schema(rule); try { @@ -39,6 +50,11 @@ class ConfigCommand extends BaseSubCommand { } } + /** + * Handles the list operation and returns the processed content as a string. + * @param {any} content - The content to process. + * @returns {string} The processed content. + */ handleList(content) { return Object.entries(content) .filter(([, value]) => { @@ -50,9 +66,15 @@ class ConfigCommand extends BaseSubCommand { .map(([key, value]) => `${key}=${value}\n`) .join(''); } - + /** + * Executes the command. + * @param {Command} commander - The commander instance. + * @param {...any} args - Additional arguments. + * @returns {Promise} A promise that resolves when the command execution is complete. + */ async run(commander, ...args) { this.setCustomPrompts(true); + // @ts-ignore const { subOptions } = await super.run(commander, ...args); // todo: specified which .aelfrc file to read or write const { flag, key, value } = subOptions; @@ -85,6 +107,7 @@ class ConfigCommand extends BaseSubCommand { switch (flag) { case 'get': result = this.rc.getOption(key); + // @ts-ignore logger.info(result); break; case 'set': @@ -102,6 +125,7 @@ class ConfigCommand extends BaseSubCommand { } } catch (e) { this.oraInstance.fail('Failed!'); + // @ts-ignore logger.error(e); } } diff --git a/src/command/console.js b/src/command/console.js index af8bd2b..d4e1fee 100644 --- a/src/command/console.js +++ b/src/command/console.js @@ -1,7 +1,3 @@ -/** - * @file console command - * @author atom-yang - */ import repl from 'repl'; import AElf from 'aelf-sdk'; import columnify from 'columnify'; @@ -10,12 +6,29 @@ import BaseSubCommand from './baseSubCommand.js'; import { getWallet } from '../utils/wallet.js'; import { logger } from '../utils/myLogger.js'; +/** + * @typedef {import('commander').Command} Command + * @typedef {import('../../types/rc/index.js').default} Registry + */ class ConsoleCommand extends BaseSubCommand { + /** + * Constructs a new ConsoleCommand instance. + * @param {Registry} rc - The registry instance. + * @param {string} [name] - The name of the command. + * @param {string} [description] - The description of the command. + */ constructor(rc, name = 'console', description = 'Open a node REPL') { super(name, [], description, [], [''], rc); } + /** + * Executes the command. + * @param {Command} commander - The commander instance. + * @param {...any} args - Additional arguments. + * @returns {Promise} A promise that resolves when the command execution is complete. + */ async run(commander, ...args) { + // @ts-ignore const { options } = await super.run(commander, ...args); const { datadir, account, password, endpoint } = options; try { @@ -45,7 +58,9 @@ class ConsoleCommand extends BaseSubCommand { } } ); + // @ts-ignore logger.info('Welcome to aelf interactive console. Ctrl + C to terminate the program. Double tap Tab to list objects'); + // @ts-ignore logger.info( boxen(columns, { padding: 1, @@ -61,6 +76,7 @@ class ConsoleCommand extends BaseSubCommand { r.context._account = wallet; } catch (e) { this.oraInstance.fail('Failed!'); + // @ts-ignore logger.error(e); } } diff --git a/src/command/create.js b/src/command/create.js index 6eeefe2..b1f09e0 100644 --- a/src/command/create.js +++ b/src/command/create.js @@ -1,7 +1,3 @@ -/** - * @file create command - * @author atom-yang - */ import AElf from 'aelf-sdk'; import chalk from 'chalk'; import BaseSubCommand from './baseSubCommand.js'; @@ -16,8 +12,16 @@ const createCommandValidatorDesc = { required: false } }; +/** + * @typedef {import('commander').Command} Command + * @typedef {import('../../types/rc/index.js').default} Registry + */ class CreateCommand extends BaseSubCommand { + /** + * Constructs a new CreateCommand instance. + * @param {Registry} rc - The registry instance. + */ constructor(rc) { super( 'create', @@ -35,15 +39,26 @@ class CreateCommand extends BaseSubCommand { createCommandValidatorDesc ); } - + /** + * Executes the create command. + * @param {Command} commander - The commander instance. + * @param {...any} args - Additional arguments. + * @returns {Promise} A promise that resolves when the command execution is complete. + */ async run(commander, ...args) { const wallet = AElf.wallet.createNewWallet(); wallet.publicKey = wallet.keyPair.getPublic().encode('hex'); + // @ts-ignore logger.info('Your wallet info is :'); + // @ts-ignore logger.info(`Mnemonic : ${wallet.mnemonic}`); + // @ts-ignore logger.info(`Private Key : ${wallet.privateKey}`); + // @ts-ignore logger.info(`Public Key : ${wallet.publicKey}`); + // @ts-ignore logger.info(`Address : ${wallet.address}`); + // @ts-ignore const { localOptions, options, subOptions } = await super.run(commander, ...args); const { datadir } = options; const { saveToFile } = subOptions; @@ -57,6 +72,7 @@ class CreateCommand extends BaseSubCommand { } } catch (e) { this.oraInstance.fail('Failed!'); + // @ts-ignore logger.error(e); } } diff --git a/src/command/dappServer/HKDF.js b/src/command/dappServer/HKDF.js index 124ac3a..11dc6b9 100644 --- a/src/command/dappServer/HKDF.js +++ b/src/command/dappServer/HKDF.js @@ -1,8 +1,3 @@ -/** - * @file hkdf - * @author atom-yang - * @link https://asecuritysite.com/encryption/HKDF - */ import { createHmac } from 'crypto'; class HKDF { @@ -24,7 +19,14 @@ class HKDF { this.prk = hmac.digest(); } + /** + * Expands the pseudorandom key to the desired length. + * @param {string} [info] - Optional context and application-specific information. + * @param {number} [size] - The length of the output keying material in bytes. + * @returns {Buffer} - The expanded keying material. + */ expand(info = '', size = 32) { + /** @type {string | Buffer} */ let pre = ''; const output = []; const numBlocks = Math.ceil(size / this.hashLength); @@ -39,7 +41,10 @@ class HKDF { return Buffer.concat(output, size); } } - +/** + * A static property that maps hash algorithm names to their output lengths in bytes. + * @type {{ [key: string]: number }} + */ HKDF.hashList = { sha256: 32, sha224: 54, diff --git a/src/command/dappServer/constants.js b/src/command/dappServer/constants.js index 0e41c9d..b48994b 100644 --- a/src/command/dappServer/constants.js +++ b/src/command/dappServer/constants.js @@ -1,8 +1,3 @@ -/** - * @file constants - * @author atom-yang - */ - const CHAIN_APIS = { '/api/blockChain/chainStatus': 'getChainStatus', '/api/blockChain/blockState': 'getChainState', diff --git a/src/command/dappServer/encrypt.js b/src/command/dappServer/encrypt.js index 1bc80a6..ea2401d 100644 --- a/src/command/dappServer/encrypt.js +++ b/src/command/dappServer/encrypt.js @@ -1,7 +1,3 @@ -/** - * @file encrypt channel - * @author atom-yang - */ import Crypto from 'crypto'; import elliptic from 'elliptic'; import { randomId } from '../../utils/utils.js'; @@ -16,6 +12,13 @@ const keyPairs = { }; class Encrypt { + /** + * Creates an instance of Encrypt. + * @param {string} algorithm - The algorithm to use for encryption. + * @param {string} remotePublicKey - The public key of the remote party. + * @param {string} random - A random string used for key generation. + * @param {string} [cipher] - The cipher to use for encryption (optional). + */ constructor(algorithm, remotePublicKey, random, cipher = defaultCipher) { if (!keyPairs[algorithm]) { keyPairs[algorithm] = elliptic.ec(algorithm).genKeyPair(); @@ -29,14 +32,14 @@ class Encrypt { } /** - * encrypt data - * @param {WindowBase64} data - * @return {{encryptedResult: string, iv: string}} + * Encrypts data using the specified algorithm and shared key. + * @param {string} data - The data to encrypt. + * @returns {{ encryptedResult: string, iv: string }} - The encrypted data and initialization vector. */ encrypt(data) { const iv = randomId(); const cipher = Crypto.createCipheriv(this.cipher, this.derivedKey, Buffer.from(iv, 'hex')); - let encrypted = cipher.update(Buffer.from(data, 'base64'), null, 'base64'); + let encrypted = cipher.update(Buffer.from(data, 'base64'), undefined, 'base64'); encrypted += cipher.final('base64'); return { encryptedResult: encrypted, @@ -45,10 +48,10 @@ class Encrypt { } /** - * decrypt data - * @param {WindowBase64} encrypted - * @param {string} iv initial vector, hex string - * @return {string} result, base64 string + * Decrypts data using the specified algorithm and shared key. + * @param {string} encrypted - The encrypted data to decrypt. + * @param {string} iv - The initialization vector used during encryption. + * @returns {string} - The decrypted data. */ decrypt(encrypted, iv) { const decipher = Crypto.createDecipheriv(this.cipher, this.derivedKey, Buffer.from(iv, 'hex')); @@ -57,7 +60,8 @@ class Encrypt { } /** - * @return {string} hex string, public key + * Gets the public key of the key pair. + * @returns {string} - The public key in hexadecimal format. */ getPublicKey() { return this.keyPair.getPublic('hex'); diff --git a/src/command/dappServer/index.js b/src/command/dappServer/index.js index c3bac43..4d89053 100644 --- a/src/command/dappServer/index.js +++ b/src/command/dappServer/index.js @@ -1,7 +1,3 @@ -/** - * @file start a dapp server - * @author atom-yang - */ import AElf from 'aelf-sdk'; import BaseSubCommand from '../baseSubCommand.js'; import { getWallet } from '../../utils/wallet.js'; @@ -17,20 +13,32 @@ const commandOptions = [ ]; const commandUsage = ['-port port', '']; - +/** + * @typedef {import('commander').Command} Command + * @typedef {import('../../../types/rc/index.js').default} Registry + */ class DeployCommand extends BaseSubCommand { + /** + * Creates an instance of DeployCommand. + * @param {Registry} rc - The registry instance. + */ constructor(rc) { super('dapp-server', [], 'Start a dAPP SOCKET.IO server', commandOptions, commandUsage, rc); } - + /** + * Runs the dappServer command. + * @param {Command} commander - The commander instance. + * @param {...any} args - Additional arguments. + * @returns {Promise} A promise that resolves when the command is complete. + */ async run(commander, ...args) { + // @ts-ignore const { options, localOptions } = await super.run(commander, ...args); const { endpoint, datadir, account, password } = options; const { port = 35443 } = localOptions; try { const aelf = new AElf(new AElf.providers.HttpProvider(endpoint)); const wallet = getWallet(datadir, account, password); - const socket = new Socket({ port, endpoint, @@ -38,9 +46,11 @@ class DeployCommand extends BaseSubCommand { wallet, address: account }); + // @ts-ignore logger.info(`DApp server is listening on port ${port}`); } catch (e) { this.oraInstance.fail('Failed!'); + // @ts-ignore logger.error(e); } } diff --git a/src/command/dappServer/sign.js b/src/command/dappServer/sign.js index ad9288f..1a4140b 100644 --- a/src/command/dappServer/sign.js +++ b/src/command/dappServer/sign.js @@ -1,7 +1,3 @@ -/** - * @file message signed and verified - * @author atom-yang - */ import elliptic from 'elliptic'; const defaultEncryptAlgorithm = 'secp256k1'; @@ -79,6 +75,10 @@ class Sign { } } + /** + * Gets the public key. + * @returns {string} The public key. + */ getPublicKey() { return this.keyPair.getPublic().encode('hex'); } diff --git a/src/command/dappServer/socket.js b/src/command/dappServer/socket.js index cf0d64c..8fe9416 100644 --- a/src/command/dappServer/socket.js +++ b/src/command/dappServer/socket.js @@ -1,7 +1,3 @@ -/** - * @file socket server - * @author atom-yang - */ import { Server } from 'socket.io'; import AElf from 'aelf-sdk'; import { interopImportCJSDefault } from 'node-cjs-interop'; @@ -14,6 +10,10 @@ import { randomId } from '../../utils/utils.js'; import { serializeMessage, deserializeMessage, checkTimestamp } from './utils.js'; import { CHAIN_APIS } from './constants.js'; +/** + * @typedef {import ('async-validator').Rules} Rules + */ +/** @type {Rules} */ const signRequestRules = { id: { required: true, @@ -46,7 +46,7 @@ const signRequestRules = { } } }; - +/** @type {Rules} */ const encryptRequestRules = { ...signRequestRules, params: { @@ -64,7 +64,7 @@ const encryptRequestRules = { } } }; - +/** @type {Rules} */ const connectSignRules = { ...signRequestRules, params: { @@ -93,7 +93,7 @@ const connectSignRules = { } } }; - +/** @type {Rules} */ const connectEncryptRules = { ...signRequestRules, params: { @@ -121,7 +121,41 @@ const encryptRequestValidator = new Schema(encryptRequestRules); const connectSignValidator = new Schema(connectSignRules); const connectEncryptValidator = new Schema(connectEncryptRules); +/** + * Represents the result of an operation. + * @typedef {Object} Result + * @property {number} code - The status code of the result. + * @property {string} msg - The message associated with the result. + * @property {any[]} error - An array of errors, if any. + * @property {any} data - The data returned by the operation. + */ + +/** + * Represents a client connected to the server. + * @typedef {Object} Client + * @property {function(string, any): void} emit - Sends an event to the client. + * @property {function(boolean=): void} disconnect - Disconnects the client. + * @property {function(string, function): void} on - Sends an event to the client. + */ + +/** + * Represents a message sent to the server. + * @typedef {Object} Message + * @property {string} id - The unique identifier for the message. + * @property {string} appId - The application ID sending the message. + * @property {string} action - The action to be performed. + * @property {any} params - The parameters for the action. + */ class Socket { + /** + * Creates an instance of Socket. + * @param {Object} options - The socket options. + * @param {number} options.port - The port to run the socket server. + * @param {string} options.endpoint - The default endpoint for the socket server. + * @param {any} options.aelf - The AElf instance. + * @param {any} options.wallet - The wallet instance. + * @param {string} options.address - The address associated with the wallet. + */ constructor(options) { const { port, endpoint, aelf, wallet, address } = options; this.aelf = aelf; @@ -132,6 +166,7 @@ class Socket { this.socket = new Server(port, { transports: ['websocket'] }); + // @ts-ignore this.socket.on('connection', this.handleConnection); this.clientConfig = { // default: { @@ -140,7 +175,13 @@ class Socket { // } }; } - + /** + * Formats the response. + * @param {string} id - The request ID. + * @param {any} [result] - The result data. + * @param {any} [errors] - The errors array. + * @returns {{id: string, result: Result}} The formatted result. + */ responseFormat(id, result, errors) { if (errors && (errors instanceof Error || (Array.isArray(errors) && errors.length > 0) || errors.Error)) { return { @@ -163,7 +204,13 @@ class Socket { } }; } - + /** + * Sends a response to the client. + * @param {Client} client - The client instance. + * @param {Result} result - The result object. + * @param {string} action - The action type. + * @param {string} appId - The application ID. + */ send(client, result, action, appId) { client.emit('bridge', result); if (action === 'disconnect') { @@ -171,10 +218,14 @@ class Socket { client.disconnect(true); } } - + /** + * Handles new client connections. + * @param {Client} client - The client instance. + */ handleConnection(client) { client.on('bridge', async data => { logger.info('Message received'); + /**@type {any} */ let result = {}; const { action, id, appId } = data; try { @@ -211,7 +262,9 @@ class Socket { } this.send(client, result, action, appId); } catch (e) { + // @ts-ignore logger.error('error happened'); + // @ts-ignore logger.error(e); result = this.responseFormat(id, {}, e.errors ? e.errors : e); if (action !== 'connect') { @@ -221,7 +274,11 @@ class Socket { } }); } - + /** + * Deserializes request parameters. + * @param {Message} request - The request message. + * @returns {Promise} The deserialized parameters. + */ async deserializeParams(request) { const { appId, params } = request; if (!this.clientConfig[appId]) { @@ -266,7 +323,11 @@ class Socket { const originalResult = serializeMessage(result); return this.clientConfig[appId]?.encrypt.encrypt(originalResult); } - + /** + * Handles connect actions. + * @param {Message} message - The message object. + * @returns {Promise} The result of the connect action. + */ async handleConnect(message) { const { appId, params } = message; const { encryptAlgorithm, publicKey } = params; @@ -315,10 +376,15 @@ class Socket { signature: responseSignature }; } + // @ts-ignore logger.error('Not support encrypt method or not enough params'); throw new Error('Not support encrypt method or not enough params'); } - + /** + * Handles method list requests. + * @param {Message} message - The message object. + * @returns {Promise} The list of methods. + */ async handleMethodList(message) { const params = await this.deserializeParams(message); const { endpoint = this.defaultEndpoint, address } = params; @@ -328,7 +394,11 @@ class Socket { .filter(v => /^[A-Z]/.test(v)) .sort(); } - + /** + * Handles API requests. + * @param {Message} message - The message object. + * @returns {Promise} The API result. + */ async handleApi(message) { const params = await this.deserializeParams(message); const { endpoint = this.defaultEndpoint, apiPath, arguments: apiArgs, methodName } = params; @@ -340,7 +410,11 @@ class Socket { const result = await this.aelf.chain[methodName](...apiArgs.map(v => v.value)); return result; } - + /** + * Handles account actions. + * @param {Message} message - The message object. + * @returns {Promise} The account result. + */ async handleAccount(message) { logger.info('Querying account information'); await this.deserializeParams(message); @@ -362,7 +436,12 @@ class Socket { ] }; } - + /** + * Handles invoke actions. + * @param {Message} message - The message object. + * @param {boolean} isReadOnly - If the invoke action is read-only. + * @returns {Promise} The invoke result. + */ async handleInvoke(message, isReadOnly) { const params = await this.deserializeParams(message); const { endpoint = this.defaultEndpoint, contractAddress, contractMethod, arguments: contractArgs } = params; @@ -381,6 +460,11 @@ class Socket { return result; } + /** + * Handles disconnect actions. + * @param {Message} message - The message object. + * @returns {Promise<{}>} The result of the disconnect action. + */ async handleDisconnect(message) { // just to verify client await this.deserializeParams(message); diff --git a/src/command/dappServer/utils.js b/src/command/dappServer/utils.js index 812a96e..b642029 100644 --- a/src/command/dappServer/utils.js +++ b/src/command/dappServer/utils.js @@ -1,8 +1,8 @@ /** - * @file utils - * @author atom-yang + * Serializes a message object to a string. + * @param {any} data - The message object to serialize. + * @returns {string} The serialized message as a string. */ - const serializeMessage = data => { let result = JSON.stringify(data); if (data === null || data === undefined) { @@ -11,6 +11,11 @@ const serializeMessage = data => { return Buffer.from(encodeURIComponent(result)).toString('base64'); }; +/** + * Deserializes a string to a message object. + * @param {string} str - The string to deserialize. + * @returns {any} The deserialized message object. + */ const deserializeMessage = str => { let result = decodeURIComponent(Buffer.from(str, 'base64').toString()); try { @@ -18,7 +23,12 @@ const deserializeMessage = str => { } catch (e) {} return result; }; - +/** + * Checks if the given timestamp is within the specified time buffer from the current time. + * @param {string} time - The timestamp to check, either as a number or a string. + * @param {number} [timeBuffer=300] - The time buffer in seconds. Default is 300 seconds. + * @returns {boolean} True if the timestamp is within the time buffer, false otherwise. + */ const checkTimestamp = (time, timeBuffer = 4 * 60) => { const checkTime = parseInt(time, 10); if (!checkTime) { diff --git a/src/command/deploy.js b/src/command/deploy.js index af85d83..39311dd 100644 --- a/src/command/deploy.js +++ b/src/command/deploy.js @@ -1,7 +1,3 @@ -/** - * @file deploy contract - * @author atom-yang - */ import chalk from 'chalk'; import BaseSubCommand from './baseSubCommand.js'; import { deployCommandParameters, deployCommandUsage } from '../utils/constants.js'; @@ -11,12 +7,24 @@ const tips = chalk.redBright( chalk.yellowBright('`aelf-command send`'), ', check details in aelf-command `README.md`' ); - +/** + * @typedef {import('../../types/rc/index.js').default} Registry + */ class DeployCommand extends BaseSubCommand { + /** + * Constructs a new DeployCommand instance. + * @param {Registry} rc - The registry instance. + * @param {string} [name] - The name of the command. + * @param {string} [description] - The description of the command. + * @param {string[]} [usage] - The usage information for the command. + */ constructor(rc, name = 'deploy', description = tips, usage = deployCommandUsage) { super(name, deployCommandParameters, description, [], usage, rc); } - + /** + * Executes the deploy command. + * @returns {Promise} A promise that resolves when the command execution is complete. + */ async run() { console.log(tips); } diff --git a/src/command/event.js b/src/command/event.js index 1d7cd0d..4790ec3 100644 --- a/src/command/event.js +++ b/src/command/event.js @@ -1,13 +1,18 @@ -/** - * @file Deserialize transactions result Logs - * @author atom-yang - */ import AElf from 'aelf-sdk'; import BaseSubCommand from './baseSubCommand.js'; import { commonGlobalOptionValidatorDesc, eventCommandParameters, eventCommandUsage } from '../utils/constants.js'; import { deserializeLogs } from '../utils/utils.js'; import { logger, plainLogger } from '../utils/myLogger.js'; +/** + * @typedef {import('commander').Command} Command + * @typedef {import('../../types/rc/index.js').default} Registry + */ class EventCommand extends BaseSubCommand { + /** + * Constructs a new EventCommand instance. + * @param {Registry} rc - The registry instance. + */ + constructor(rc) { super( 'event', @@ -19,8 +24,14 @@ class EventCommand extends BaseSubCommand { commonGlobalOptionValidatorDesc ); } - + /** + * Executes the event command. + * @param {Command} commander - The commander instance. + * @param {...any} args - Additional arguments. + * @returns {Promise} A promise that resolves when the command execution is complete. + */ async run(commander, ...args) { + // @ts-ignore const { options, subOptions } = await super.run(commander, ...args); const { endpoint } = options; try { @@ -29,8 +40,10 @@ class EventCommand extends BaseSubCommand { const txResult = await aelf.chain.getTxResult(txId); // console.log(plainLogger.info(`Transaction ${txId}'s Logs: \n ${JSON.stringify(txResult.Logs, null, 2)}`)); if (!txResult.Status || txResult.Status.toUpperCase() !== 'MINED') { + // @ts-ignore console.log(plainLogger.info(`Transaction ${txId} is not mined`)); } else if (!txResult.Logs) { + // @ts-ignore console.log(plainLogger.info(`Transaction ${txId} returns void`)); } else { this.oraInstance.start('Deserialize Transaction Logs...'); @@ -43,12 +56,14 @@ class EventCommand extends BaseSubCommand { this.oraInstance.clear(); console.log( + // @ts-ignore `\n${plainLogger.info(`\nThe results returned by \nTransaction: ${txId} is: \n${JSON.stringify(logs, null, 2)}`)}` ); this.oraInstance.succeed('Succeed!'); } } catch (e) { this.oraInstance.fail('Failed!'); + // @ts-ignore logger.error(e); } } diff --git a/src/command/getBlkHeight.js b/src/command/getBlkHeight.js index 5ebca3f..73ad191 100644 --- a/src/command/getBlkHeight.js +++ b/src/command/getBlkHeight.js @@ -1,18 +1,29 @@ -/** - * @file get block height - * @author atom-yang - */ import AElf from 'aelf-sdk'; import BaseSubCommand from './baseSubCommand.js'; import { commonGlobalOptionValidatorDesc } from '../utils/constants.js'; import { logger } from '../utils/myLogger.js'; +/** + * @typedef {import('commander').Command} Command + * @typedef {import('../../types/rc/index.js').default} Registry + */ class GetBlkHeightCommand extends BaseSubCommand { + /** + * Constructs a new GetBlkHeightCommand instance. + * @param {Registry} rc - The registry instance. + */ constructor(rc) { super('get-blk-height', [], 'Get the current block height of specified chain', [], [''], rc, commonGlobalOptionValidatorDesc); } + /** + * Executes the get block height command. + * @param {Command} commander - The commander instance. + * @param {...any} args - Additional arguments. + * @returns {Promise} A promise that resolves when the command execution is complete. + */ async run(commander, ...args) { + // @ts-ignore const { options } = await super.run(commander, ...args); const aelf = new AElf(new AElf.providers.HttpProvider(options.endpoint)); try { @@ -22,6 +33,7 @@ class GetBlkHeightCommand extends BaseSubCommand { // todo: chalk or a custom reporter } catch (e) { this.oraInstance.fail('Failed!'); + // @ts-ignore logger.error(e); } } diff --git a/src/command/getBlkInfo.js b/src/command/getBlkInfo.js index 6a5b6a6..2b4e13e 100644 --- a/src/command/getBlkInfo.js +++ b/src/command/getBlkInfo.js @@ -1,7 +1,3 @@ -/** - * @file get block info - * @author atom-yang - */ import AElf from 'aelf-sdk'; import { interopImportCJSDefault } from 'node-cjs-interop'; import asyncValidator from 'async-validator'; @@ -10,7 +6,17 @@ import BaseSubCommand from './baseSubCommand.js'; import { commonGlobalOptionValidatorDesc, blkInfoCommandParameters, blkInfoCommandUsage } from '../utils/constants.js'; import { logger } from '../utils/myLogger.js'; +/** + * @typedef {import('commander').Command} Command + * @typedef {import('async-validator').Rules} Rules + * @typedef {import('async-validator').Values} Values + * @typedef {import('../../types/rc/index.js').default} Registry + */ class GetBlkInfoCommand extends BaseSubCommand { + /** + * Constructs a new GetBlkInfoCommand instance. + * @param {Registry} rc - The registry instance. + */ constructor(rc) { super( 'get-blk-info', @@ -22,7 +28,12 @@ class GetBlkInfoCommand extends BaseSubCommand { commonGlobalOptionValidatorDesc ); } - + /** + * Validates the provided parameters against the given rules. + * @param {Rules} rule - The validation rules. + * @param {Values} parameters - The parameters to validate. + * @returns {Promise} A promise that resolves when validation is complete. + */ async validateParameters(rule, parameters) { const validator = new Schema(rule); try { @@ -31,8 +42,14 @@ class GetBlkInfoCommand extends BaseSubCommand { this.handleUniOptionsError(e); } } - + /** + * Executes the get block info command. + * @param {Command} commander - The commander instance. + * @param {...any} args - Additional arguments. + * @returns {Promise} A promise that resolves when the command execution is complete. + */ async run(commander, ...args) { + // @ts-ignore const { options, subOptions } = await super.run(commander, ...args); await this.validateParameters( { @@ -60,9 +77,11 @@ class GetBlkInfoCommand extends BaseSubCommand { blockInfo = await aelf.chain.getBlockByHeight(height, includeTxs); } this.oraInstance.succeed('Succeed!'); + // @ts-ignore logger.info(blockInfo); } catch (e) { this.oraInstance.fail('Failed!'); + // @ts-ignore logger.error(e); } } diff --git a/src/command/getChainStatus.js b/src/command/getChainStatus.js index 3218038..e109c7d 100644 --- a/src/command/getChainStatus.js +++ b/src/command/getChainStatus.js @@ -1,18 +1,29 @@ -/** - * @file get block height - * @author atom-yang - */ import AElf from 'aelf-sdk'; import BaseSubCommand from './baseSubCommand.js'; import { commonGlobalOptionValidatorDesc } from '../utils/constants.js'; import { logger } from '../utils/myLogger.js'; +/** + * @typedef {import('commander').Command} Command + * @typedef {import('../../types/rc/index.js').default} Registry + */ class GetChainStatusCommand extends BaseSubCommand { + /** + * Constructs a new GetChainStatusCommand instance. + * @param {Registry} rc - The registry instance. + */ constructor(rc) { super('get-chain-status', [], 'Get the current chain status', [], [], rc, commonGlobalOptionValidatorDesc); } + /** + * Executes the get chain status command. + * @param {Command} commander - The commander instance. + * @param {...any} args - Additional arguments. + * @returns {Promise} A promise that resolves when the command execution is complete. + */ async run(commander, ...args) { + // @ts-ignore const { options } = await super.run(commander, ...args); const aelf = new AElf(new AElf.providers.HttpProvider(options.endpoint)); try { @@ -21,6 +32,7 @@ class GetChainStatusCommand extends BaseSubCommand { this.oraInstance.succeed(`Succeed\n${JSON.stringify(status, null, 2)}`); } catch (e) { this.oraInstance.fail('Failed!'); + // @ts-ignore logger.error(e); } } diff --git a/src/command/getTxResult.js b/src/command/getTxResult.js index 8342f0c..28870e2 100644 --- a/src/command/getTxResult.js +++ b/src/command/getTxResult.js @@ -1,7 +1,3 @@ -/** - * @file get block height - * @author atom-yang - */ import AElf from 'aelf-sdk'; import { interopImportCJSDefault } from 'node-cjs-interop'; import asyncValidator from 'async-validator'; @@ -10,7 +6,17 @@ import BaseSubCommand from './baseSubCommand.js'; import { commonGlobalOptionValidatorDesc, txResultCommandParameters, txResultCommandUsage } from '../utils/constants.js'; import { logger } from '../utils/myLogger.js'; +/** + * @typedef {import('commander').Command} Command + * @typedef {import('async-validator').Rules} Rules + * @typedef {import('async-validator').Values} Values + * @typedef {import('../../types/rc/index.js').default} Registry + */ class GetTxResultCommand extends BaseSubCommand { + /** + * Constructs a new GetTxResultCommand instance. + * @param {Registry} rc - The registry instance. + */ constructor(rc) { super( 'get-tx-result', @@ -23,6 +29,12 @@ class GetTxResultCommand extends BaseSubCommand { ); } + /** + * Validates the parameters against given rules. + * @param {Rules} rule - The validation rules. + * @param {Values} parameters - The parameters to validate. + * @returns {Promise} A promise that resolves when validation is complete. + */ async validateParameters(rule, parameters) { const validator = new Schema(rule); try { @@ -32,7 +44,14 @@ class GetTxResultCommand extends BaseSubCommand { } } + /** + * Executes the get transaction result command. + * @param {Command} commander - The commander instance. + * @param {...any} args - Additional arguments. + * @returns {Promise} A promise that resolves when the command execution is complete. + */ async run(commander, ...args) { + // @ts-ignore const { options, subOptions } = await super.run(commander, ...args); try { await this.validateParameters( @@ -50,9 +69,11 @@ class GetTxResultCommand extends BaseSubCommand { this.oraInstance.start(); const txResult = await aelf.chain.getTxResult(txId); this.oraInstance.succeed('Succeed!'); + // @ts-ignore logger.info(JSON.stringify(txResult, null, 2)); } catch (e) { this.oraInstance.fail('Failed to run this command'); + // @ts-ignore logger.error(e); } } diff --git a/src/command/load.js b/src/command/load.js index 8129b0c..12e4d5d 100644 --- a/src/command/load.js +++ b/src/command/load.js @@ -1,7 +1,3 @@ -/** - * @file load wallet from command argv - * @author atom-yang - */ import AElf from 'aelf-sdk'; import BaseSubCommand from './baseSubCommand.js'; import { commonGlobalOptionValidatorDesc, loadCommandParameters, loadCommandUsage } from '../utils/constants.js'; @@ -16,7 +12,15 @@ const loadCommandValidatorDesc = { } }; +/** + * @typedef {import('commander').Command} Command + * @typedef {import('../../types/rc/index.js').default} Registry + */ class LoadCommand extends BaseSubCommand { + /** + * Constructs a new LoadCommand instance. + * @param {Registry} rc - The registry instance. + */ constructor(rc) { super( 'load', @@ -29,12 +33,20 @@ class LoadCommand extends BaseSubCommand { ); } + /** + * Executes the load command. + * @param {Command} commander - The commander instance. + * @param {...any} args - Additional arguments. + * @returns {Promise} A promise that resolves when the command execution is complete. + */ async run(commander, ...args) { + // @ts-ignore const { options, subOptions } = await super.run(commander, ...args); const { datadir } = options; const { privateKey, saveToFile, createdByOld } = subOptions; try { let wallet = null; + // @ts-ignore logger.info('Your wallet info is :'); if (privateKey.trim().split(' ').length > 1) { if (createdByOld) { @@ -43,13 +55,17 @@ class LoadCommand extends BaseSubCommand { return; } wallet = AElf.wallet.getWalletByMnemonic(privateKey.trim()); + // @ts-ignore logger.info(`Mnemonic : ${wallet.mnemonic}`); } else { wallet = AElf.wallet.getWalletByPrivateKey(privateKey.trim()); } wallet.publicKey = wallet.keyPair.getPublic().encode('hex'); + // @ts-ignore logger.info(`Private Key : ${wallet.privateKey}`); + // @ts-ignore logger.info(`Public Key : ${wallet.publicKey}`); + // @ts-ignore logger.info(`Address : ${wallet.address}`); if (saveToFile === true || saveToFile === 'true') { const keyStorePath = await saveKeyStore(wallet, datadir); @@ -59,6 +75,7 @@ class LoadCommand extends BaseSubCommand { } } catch (e) { this.oraInstance.fail('Failed!'); + // @ts-ignore logger.error(e); } } diff --git a/src/command/proposal.js b/src/command/proposal.js index 9b55777..0015f63 100644 --- a/src/command/proposal.js +++ b/src/command/proposal.js @@ -1,7 +1,3 @@ -/** - * @file call read-only method on contract - * @author atom-yang - */ import assert from 'assert'; import AElf from 'aelf-sdk'; import moment from 'moment'; @@ -22,10 +18,22 @@ import { import { getWallet } from '../utils/wallet.js'; import { logger } from '../utils/myLogger.js'; +/** + * @typedef {import('commander').Command} Command + * @typedef {import('async-validator').Rules} Rules + * @typedef {import('async-validator').Values} Values + * @typedef {import ('inquirer').InputQuestion } InputQuestion + * @typedef {import ('inquirer').ListQuestion } ListQuestion + * @typedef {import('../../types/rc/index.js').default} Registry + */ +/** + * @type {Array} + */ const toContractPrompts = [ { type: 'input', name: 'contract-address', + // @ts-ignore extraName: ['contract-name'], message: 'Enter a contract address or name', suffix: ':' @@ -55,6 +63,15 @@ async function getContractAddress(aelf, wallet, address) { } class ProposalCommand extends BaseSubCommand { + /** + * Constructs a new ProposalCommand instance. + * @param {Registry} rc - The registry instance. + * @param {string} [name] - Optional name of the command. + * @param {string} [description] - Optional description of the command. + * @param {Array} [parameters] - Optional array of parameter objects. + * @param {string[]} [usage] - Optional array of usage strings. + * @param {any[]} [options] - Optional array of options. + */ constructor( rc, name = 'proposal', @@ -67,6 +84,13 @@ class ProposalCommand extends BaseSubCommand { this.aelfMock = {}; } + /** + * Processes address after prompting. + * @param {any} aelf - The AElf instance. + * @param {any} wallet - The wallet instance. + * @param {{ [key: string]: any }} answerInput - Input from the user. + * @returns {Promise} A promise that resolves with the processed address. + */ async processAddressAfterPrompt(aelf, wallet, answerInput) { this.toContractAddress = await getContractAddress(aelf, wallet, answerInput['contract-address']); let { contractAddress } = BaseSubCommand.normalizeConfig(answerInput); @@ -74,12 +98,19 @@ class ProposalCommand extends BaseSubCommand { return contractAddress; } + /** + * Runs the ProposalCommand. + * @param {Command} commander - The commander instance. + * @param {...any} args - Additional arguments. + * @returns {Promise} A promise that resolves when the command execution is complete. + */ async run(commander, ...args) { + // @ts-ignore const { options, subOptions } = await super.run(commander, ...args); const { endpoint, datadir, account, password } = options; const { descriptionUrl, proposalContract, organization, expiredTime } = subOptions; try { - if (!proposalCommandParameters[0].choices.includes(proposalContract)) { + if (!proposalCommandParameters[0].choices?.includes(proposalContract)) { throw new Error( // eslint-disable-next-line max-len `${proposalContract} is not in the list of proposal contracts, choice one of \`AElf.ContractNames.Parliament\`, \`AElf.ContractNames.Referendum\` and \`AElf.ContractNames.Association\`` @@ -144,11 +175,13 @@ class ProposalCommand extends BaseSubCommand { proposalDescriptionUrl: descriptionUrl }) ); + // @ts-ignore logger.info(txId); this.oraInstance.start('loading proposal id...'); const tx = await getTxResult(aelf, txId.TransactionId); this.oraInstance.succeed(); if (tx.Status === 'PENDING') { + // @ts-ignore logger.info( `Transaction is still pending, you can get proposal id later by running ${chalk.yellow( `aelf-command event ${txId.TransactionId}` @@ -161,11 +194,13 @@ class ProposalCommand extends BaseSubCommand { (Logs || []).filter(v => v.Name === 'ProposalCreated') ); assert.strictEqual(results.length, 1, 'No related log'); + // @ts-ignore logger.info(`Proposal id: ${results[0].proposalId}.`); this.oraInstance.succeed('Succeed!'); } } catch (e) { this.oraInstance.fail('Failed!'); + // @ts-ignore logger.fatal(e); } } diff --git a/src/command/send.js b/src/command/send.js index b1693fb..02c4f62 100644 --- a/src/command/send.js +++ b/src/command/send.js @@ -1,14 +1,23 @@ -/** - * @file call read-only method on contract - * @author atom-yang - */ import CallCommand from './call.js'; +/** + * @typedef {import('../../types/rc/index.js').default} Registry + */ class SendCommand extends CallCommand { + /** + * Creates an instance of SendCommand. + * @param {Registry} rc - The registry instance. + */ constructor(rc) { super(rc, 'send', 'Execute a method on a contract.'); } + /** + * Asynchronously calls a method and handles transaction progress. + * @param {any} method - The method to call. + * @param {any} params - The parameters for the method. + * @returns {Promise} A promise that resolves to the result of the method call. + */ async callMethod(method, params) { this.oraInstance.start('sending the transaction'); const result = await method(params); diff --git a/src/command/wallet.js b/src/command/wallet.js index 1d773d6..05f9bb1 100644 --- a/src/command/wallet.js +++ b/src/command/wallet.js @@ -1,7 +1,3 @@ -/** - * @file show wallet info - * @author atom-yang - */ import BaseSubCommand from './baseSubCommand.js'; import { commonGlobalOptionValidatorDesc } from '../utils/constants.js'; import { getWallet } from '../utils/wallet.js'; @@ -22,8 +18,15 @@ const walletCommandValidatorDesc = { required: true } }; - +/** + * @typedef {import('commander').Command} Command + * @typedef {import('../../types/rc/index.js').default} Registry + */ class WalletCommand extends BaseSubCommand { + /** + * Constructs a new WalletCommand instance. + * @param {Registry} rc - The Registry instance for configuration. + */ constructor(rc) { super( 'wallet', @@ -36,21 +39,33 @@ class WalletCommand extends BaseSubCommand { ); } + /** + * Runs the wallet command logic. + * @param {Command} commander - The Commander instance for command handling. + * @param {...any} args - Additional arguments for command execution. + * @returns {Promise} A promise that resolves when the command execution is complete. + */ async run(commander, ...args) { + // @ts-ignore const { options } = await super.run(commander, ...args); const { datadir, account, password } = options; try { const wallet = getWallet(datadir, account, password); if (wallet.mnemonic) { + // @ts-ignore logger.info(`Mnemonic : ${wallet.mnemonic}`); } wallet.publicKey = wallet.keyPair.getPublic().encode('hex'); + // @ts-ignore logger.info(`Private Key : ${wallet.privateKey}`); + // @ts-ignore logger.info(`Public Key : ${wallet.publicKey}`); + // @ts-ignore logger.info(`Address : ${wallet.address}`); this.oraInstance.succeed('Succeed!'); } catch (e) { this.oraInstance.fail('Failed!'); + // @ts-ignore logger.error(e); } } diff --git a/src/rc/index.js b/src/rc/index.js index a98a461..3111902 100644 --- a/src/rc/index.js +++ b/src/rc/index.js @@ -1,7 +1,3 @@ -/** - * @file command config operations - * @author atom-yang - */ import path from 'path'; import fs from 'fs'; import { mkdirpSync } from 'mkdirp'; @@ -28,10 +24,20 @@ class Registry { if (!fs.existsSync(path.resolve(userHomeDir, 'aelf'))) { mkdirpSync(path.resolve(userHomeDir, 'aelf')); } + /** + * AELF configuration object. + * @type {Object.} + */ this.aelfConfig = {}; this.init(); } + /** + * Retrieves file content or default content if file doesn't exist. + * @param {string} file - The file path. + * @param {string} [defaultContent] - Optional default content if file doesn't exist. + * @returns {*} The content of the file or default content. + */ static getFileOrNot(file, defaultContent = '') { if (fs.existsSync(file)) { return fs.readFileSync(file).toString(); @@ -39,6 +45,11 @@ class Registry { return defaultContent; } + /** + * Retrieves file content or creates the file if it doesn't exist. + * @param {string} file - The file path. + * @returns {string} The file content or an empty string if the file is created. + */ static getFileOrCreate(file) { if (fs.existsSync(file)) { return fs.readFileSync(file).toString(); @@ -46,7 +57,11 @@ class Registry { fs.writeFileSync(file, rcHeader); return ''; } - + /** + * Loads configuration from provided content. + * @param {string} [content] - Optional content to load configuration from. + * @returns {Object.} The loaded configuration object. + */ static loadConfig(content = '') { const result = {}; content @@ -58,7 +73,10 @@ class Registry { }); return result; } - + /** + * Retrieves configuration from environment variables. + * @returns {Object.} The configuration object retrieved from environment variables. + */ static getConfigFromEnv() { const result = {}; Object.entries(ENV_RC_KEYS).forEach(([key, value]) => { @@ -70,16 +88,19 @@ class Registry { } /** - * obj only contains one level field - * @param {Object} obj - * @return {string[]} the array of content + * Converts a one-level object into an array of content. + * @param {Object.} [obj] - The object to stringify. + * @returns {string[]} Array of content from the object's fields. */ static stringify(obj = {}) { let result = Object.entries(obj).map(([key, value]) => `${key} ${value}`); result = rcHeader.split('\n').concat(result); return result; } - + /** + * Initializes and retrieves initial configuration options. + * @returns {{ endpoint: string, datadir: string, password: string, account: string }} Initial configuration values. + */ init() { const pwdRc = Registry.loadConfig(Registry.getFileOrNot(path.resolve(process.cwd(), '.aelfrc'))); const globalRc = Registry.loadConfig(Registry.getFileOrCreate(this.globalConfigLoc)); @@ -94,14 +115,31 @@ class Registry { return rc; } + /** + * Retrieves a configuration option by key. + * @param {string} key - The option key. + * @returns {*} The value of the option. + */ getOption(key) { return this.aelfConfig[key]; } + /** + * Sets a configuration option. + * @param {string} key - The option key. + * @param {*} value - The value to set. + */ setOption(key, value) { this.aelfConfig[key] = value; } + /** + * Saves an option to configuration file. + * @param {string} key - The option key. + * @param {*} value - The value to save. + * @param {*} [filePath] - Optional file path to save configuration. + * @returns {*} Result of saving operation. + */ saveOption(key, value, filePath = this.globalConfigLoc) { this.aelfConfig[key] = value; const rc = Registry.loadConfig(Registry.getFileOrCreate(filePath)); @@ -109,16 +147,30 @@ class Registry { return fs.writeFileSync(filePath, `${Registry.stringify(rc).join('\n')}\n`); } + /** + * Deletes a configuration key from file. + * @param {string} key - The option key to delete. + * @param {*} [filePath] - Optional file path to delete from. + * @returns {*} Result of deletion operation. + */ deleteConfig(key, filePath = this.globalConfigLoc) { const rc = Registry.loadConfig(Registry.getFileOrCreate(filePath)); delete rc[key]; return fs.writeFileSync(filePath, `${Registry.stringify(rc).join('\n')}\n`); } + /** + * Retrieves configurations from file. + * @param {string} [filePath] - Optional file path to retrieve configurations. + * @returns {Object.} The configurations retrieved from file. + */ getFileConfigs(filePath = this.globalConfigLoc) { return Registry.loadConfig(Registry.getFileOrCreate(filePath)); } - + /** + * Retrieves all configurations. + * @returns {Object.} All configurations. + */ getConfigs() { return this.aelfConfig; } diff --git a/src/utils/Logger.js b/src/utils/Logger.js index 9c68846..2c2fa1b 100644 --- a/src/utils/Logger.js +++ b/src/utils/Logger.js @@ -34,9 +34,25 @@ const levels = [ ]; class Logger { + /** + * Constructs a new Logger instance. + * @param {Object.} props - Logger properties. + */ constructor(props) { + /** + * Symbol for the logger. + * @type {string} + */ this.symbol = ''; + /** + * Name of the logger. + * @type {string} + */ this.name = ''; + /** + * Determines whether to log messages. + * @type {boolean} + */ this.log = props.log !== undefined ? props.log : true; // determin whether console.log or not if (!props.onlyWords) { this.symbol = ''; @@ -50,6 +66,15 @@ class Logger { levels.forEach(item => { const { level, color } = item; const fnName = level.toLocaleLowerCase(); + + /** + * Logs an error message. + * @function + * @memberof Logger.prototype + * @param {string} firstParam - The first parameter to log. + * @param {...any} rest - Additional parameters to log. + * @returns {string} - The formatted log message. + */ Logger.prototype[fnName] = function fn(firstParam, ...rest) { // if (typeof params === 'obejct') params = JSON.stringify(params); diff --git a/src/utils/constants.js b/src/utils/constants.js index 4cc695c..1448bb4 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -1,7 +1,3 @@ -/** - * @file constants - * @author atom-yang - */ import path from 'path'; import moment from 'moment'; import inquirer from 'inquirer'; @@ -9,6 +5,30 @@ import { logger } from './myLogger.js'; import DatePrompt from 'inquirer-date-prompt'; import SearchList from 'inquirer-search-list'; +/** + * @typedef {import('../../types/utils/constants.js').CallCommandParameter} CallCommandParameter + * @typedef {import('../../types/utils/constants.js').PasswordValidatorDesc} PasswordValidatorDesc + * @typedef {import('../../types/utils/constants.js').EndpointValidatorDesc} EndpointValidatorDesc + * @typedef {import('../../types/utils/constants.js').DatadirValidatorDesc} DatadirValidatorDesc + * @typedef {import('../../types/utils/constants.js').AccountValidatorDesc} AccountValidatorDesc + * @typedef {import('../../types/utils/constants.js').CommonGlobalOptionValidatorDesc} CommonGlobalOptionValidatorDesc + * @typedef {import('../../types/utils/constants.js').BlkInfoCommandParameter} BlkInfoCommandParameter + * @typedef {import('../../types/utils/constants.js').TxResultCommandParameter} TxResultCommandParameter + * @typedef {import('../../types/utils/constants.js').GlobalOptionPrompt} GlobalOptionPrompt + * @typedef {import('../../types/utils/constants.js').CreateCommandParameter} CreateCommandParameter + * @typedef {import('../../types/utils/constants.js').LoadCommandParameter} LoadCommandParameter + * @typedef {import('../../types/utils/constants.js').PasswordPrompt} PasswordPrompt + * @typedef {import('../../types/utils/constants.js').DeployCommandParameter} DeployCommandParameter + * @typedef {import('../../types/utils/constants.js').ConfigCommandParameter} ConfigCommandParameter + * @typedef {import('../../types/utils/constants.js').ProposalCommandParameter} ProposalCommandParameter + * @typedef {import('../../types/utils/constants.js').EventCommandParameter} EventCommandParameter + * @typedef {'password' | 'endpoint' | 'datadir' | 'account'} CommonGlobalOptionKey + */ + +/** + * Array of usage strings for the call command. + * @type {string[]} + */ const callCommandUsages = [ ' ', ' ', @@ -16,6 +36,10 @@ const callCommandUsages = [ '' ]; +/** + * Parameters for the call command. + * @type {CallCommandParameter[]} + */ const callCommandParameters = [ { type: 'input', @@ -53,6 +77,10 @@ const callCommandParameters = [ } ]; +/** + * Parameters for the blkInfo command. + * @type {BlkInfoCommandParameter[]} + */ const blkInfoCommandParameters = [ { type: 'input', @@ -73,11 +101,20 @@ const blkInfoCommandParameters = [ } ]; +/** + * Array of usage strings for the blkInfo command. + * @type {string[]} + */ const blkInfoCommandUsage = [' ', '', '']; +// @ts-ignore inquirer.registerPrompt('datetime', DatePrompt); inquirer.registerPrompt('search-list', SearchList); +/** + * Parameters for the proposal command. + * @type {ProposalCommandParameter[]} + */ const proposalCommandParameters = [ { type: 'list', @@ -113,6 +150,10 @@ const proposalCommandParameters = [ } ]; +/** + * Array of usage strings for the proposal command. + * @type {string[]} + */ const proposalCommandUsage = [ ' ', ' ', @@ -120,6 +161,10 @@ const proposalCommandUsage = [ '' ]; +/** + * Parameters for the txResult command. + * @type {TxResultCommandParameter[]} + */ const txResultCommandParameters = [ { type: 'input', @@ -129,8 +174,16 @@ const txResultCommandParameters = [ } ]; +/** + * Array of usage strings for the txResult command. + * @type {string[]} + */ const txResultCommandUsage = ['', '']; +/** + * Parameters for the create command. + * @type {CreateCommandParameter[]} + */ const createCommandParameters = [ { type: 'confirm', @@ -144,8 +197,16 @@ const createCommandParameters = [ } ]; +/** + * Array of usage strings for the create command. + * @type {string[]} + */ const createCommandUsage = [' -c, cipher', '-c, cipher', '']; +/** + * Parameters for the config command. + * @type {ConfigCommandParameter[]} + */ const configCommandParameters = [ { type: 'input', @@ -170,8 +231,16 @@ const configCommandParameters = [ } ]; +/** + * Array of usage strings for the config command. + * @type {string[]} + */ const configCommandUsage = ['get ', 'set ', 'delete ', 'list']; +/** + * Parameters for the load command. + * @type {LoadCommandParameter[]} + */ const loadCommandParameters = [ { type: 'input', @@ -208,6 +277,10 @@ const loadCommandParameters = [ } ]; +/** + * Array of usage strings for the load command. + * @type {string[]} + */ const loadCommandUsage = [ ' ', ' ', @@ -215,6 +288,10 @@ const loadCommandUsage = [ '' ]; +/** + * Parameters for the deploy command. + * @type {DeployCommandParameter[]} + */ const deployCommandParameters = [ { type: 'input', @@ -233,8 +310,16 @@ const deployCommandParameters = [ } ]; +/** + * Array of usage strings for the deploy command. + * @type {string[]} + */ const deployCommandUsage = [' ', '', '']; +/** + * Parameters for the event command. + * @type {EventCommandParameter[]} + */ const eventCommandParameters = [ { type: 'input', @@ -244,8 +329,16 @@ const eventCommandParameters = [ } ]; +/** + * Array of usage strings for the event command. + * @type {string[]} + */ const eventCommandUsage = ['', '']; +/** + * Validator description for common global options. + * @type {CommonGlobalOptionValidatorDesc} + */ const commonGlobalOptionValidatorDesc = { password: { type: 'string', @@ -276,9 +369,10 @@ const commonGlobalOptionValidatorDesc = { } }; -const strictGlobalOptionValidatorDesc = {}; +const strictGlobalOptionValidatorDesc = /**@type {CommonGlobalOptionValidatorDesc}*/ ({}); -Object.entries(commonGlobalOptionValidatorDesc).forEach(([key, value]) => { +// @ts-ignore +Object.entries(commonGlobalOptionValidatorDesc).forEach((/** @type {[CommonGlobalOptionKey, any]} */ [key, value]) => { strictGlobalOptionValidatorDesc[key] = { ...value, required: true @@ -286,8 +380,8 @@ Object.entries(commonGlobalOptionValidatorDesc).forEach(([key, value]) => { }); /** - * specified the prompts options for CLI global options - * @type {*[]} + * Array of global option prompts. + * @type {GlobalOptionPrompt[]} */ const globalOptionsPrompts = [ { @@ -311,6 +405,10 @@ const globalOptionsPrompts = [ } ]; +/** + * Array of password prompts. + * @type {PasswordPrompt[]} + */ const passwordPrompts = [ { type: 'password', @@ -319,6 +417,7 @@ const passwordPrompts = [ message: 'Enter a password', validate(val) { if (!val || val.length <= 6) { + // @ts-ignore logger.error('\npassword is too short'); process.exit(1); } diff --git a/src/utils/fs.js b/src/utils/fs.js index f0738b0..c4f7b8a 100644 --- a/src/utils/fs.js +++ b/src/utils/fs.js @@ -1,7 +1,3 @@ -/** - * @file fs operator - * @author atom-yang - */ import fs from 'fs'; import os from 'os'; @@ -10,9 +6,22 @@ import { promisify } from './utils.js'; const readFile = promisify(fs.readFile); const writeFile = promisify(fs.writeFile); +/** + * Carriage return character code. + * @type {number} + */ const cr = '\r'.charCodeAt(0); +/** + * Line feed character code. + * @type {number} + */ const lf = '\n'.charCodeAt(0); +/** + * Retrieves the end-of-line (EOL) sequence from a file. + * @param {string} path - The path to the file. + * @returns {Promise} A promise that resolves with the EOL sequence found in the file, or undefined. + */ async function getEolFromFile(path) { if (!fs.existsSync(path)) { return undefined; @@ -31,6 +40,12 @@ async function getEolFromFile(path) { return undefined; } +/** + * Writes data to a file while preserving the original end-of-line (EOL) sequence. + * @param {string} path - The path to the file. + * @param {string} data - The data to write. + * @returns {Promise} A promise that resolves when the file is successfully written. + */ async function writeFilePreservingEol(path, data) { const eol = (await getEolFromFile(path)) || os.EOL; let result = data; diff --git a/src/utils/myLogger.js b/src/utils/myLogger.js index 3a96e05..35d0152 100644 --- a/src/utils/myLogger.js +++ b/src/utils/myLogger.js @@ -1,10 +1,17 @@ import Logger from './Logger.js'; +/** + * Instance of Logger with full logging enabled. + * @type {Logger} + */ const logger = new Logger({ name: 'AElf', log: true }); - +/** + * Instance of Logger that logs only words. + * @type {Logger} + */ const plainLogger = new Logger({ onlyWords: true, log: false diff --git a/src/utils/userHomeDir.js b/src/utils/userHomeDir.js index bbaad38..d4e5da0 100644 --- a/src/utils/userHomeDir.js +++ b/src/utils/userHomeDir.js @@ -1,12 +1,16 @@ -/** - * @file find user home dir - * @author atom-yang - */ import path from 'path'; import { homedir } from 'os'; +/** + * Path to the home directory. + * @type {string} + */ const home = homedir(); +/** + * Retrieves the user ID (UID) of the current process. + * @returns {number | null} The UID of the current process if available, otherwise null. + */ function getUid() { if (process.platform !== 'win32' && process.getuid) { return process.getuid(); @@ -14,20 +18,41 @@ function getUid() { return null; } +/** + * Checks if the current environment is a fake root environment. + * @returns {boolean} True if the environment is a fake root, false otherwise. + */ function isFakeRoot() { return Boolean(process.env.FAKEROOTKEY); } +/** + * Checks if a given user ID belongs to the root user. + * @param {number | null} uid - The user ID to check. + * @returns {boolean} True if the user ID belongs to the root user, false otherwise. + */ function isRootUser(uid) { return uid === 0; } +/** + * Checks if the current operating system is Windows. + * @returns {boolean} True if the operating system is Windows, false otherwise. + */ function isWindows() { return process.platform === 'win32'; } +/** + * Indicates whether the current environment is running as the root user. + * @type {boolean} + */ const ROOT_USER = isRootUser(getUid()) && !isFakeRoot(); +/** + * User's home directory path. + * @type {any} + */ let userHomeDir; if (isWindows()) { userHomeDir = path.resolve(home, './AppData/Local'); diff --git a/src/utils/utils.js b/src/utils/utils.js index 969aaf9..4fa9256 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -1,7 +1,3 @@ -/** - * @file utils - * @author atom-yang - */ import AElf from 'aelf-sdk'; import moment from 'moment'; import chalk from 'chalk'; @@ -13,6 +9,16 @@ import inquirer from 'inquirer'; import { plainLogger } from './myLogger.js'; import * as protobuf from '@aelfqueen/protobufjs'; +/** + * @typedef {import('ora').Ora} Ora + * @typedef {import('inquirer').DistinctQuestion} DistinctQuestion + */ +/** + * Promisifies a function. + * @param {Function} fn - The function to promisify. + * @param {boolean} [firstData] - Whether to pass first data. + * @returns {Function} A promisified function. + */ function promisify(fn, firstData) { return (...args) => new Promise((resolve, reject) => { @@ -40,6 +46,11 @@ function promisify(fn, firstData) { }); } +/** + * Converts a string to camelCase. + * @param {string} str - The input string. + * @returns {string} The camelCase version of the string. + */ function camelCase(str) { return _camelCase(str); } @@ -60,12 +71,13 @@ function isRegExp(o) { } /** - * get contract methods' keys - * @param {Object} contract contract instance - * @return {string[]} + * Retrieves the list of methods of a contract. + * @param {Object.} [contract] - The contract object. + * @returns {string[]} An array of method names. */ function getContractMethods(contract = {}) { if (!contract) { + // @ts-ignore plainLogger.fatal('There is no such contract'); process.exit(1); } @@ -74,6 +86,14 @@ function getContractMethods(contract = {}) { .sort(); } +/** + * Retrieves an instance of a contract. + * @param {string} contractAddress - The address of the contract. + * @param {any} aelf - The AElf instance. + * @param {any} wallet - The wallet instance. + * @param {Ora} oraInstance - The ora instance for logging. + * @returns {Promise} A promise that resolves to the contract instance. + */ async function getContractInstance(contractAddress, aelf, wallet, oraInstance) { if (typeof contractAddress !== 'string') { return contractAddress; @@ -90,6 +110,7 @@ async function getContractInstance(contractAddress, aelf, wallet, oraInstance) { contract = await aelf.chain.contractAt(address, wallet); } } catch (e) { + // @ts-ignore oraInstance.fail(plainLogger.error('Failed to find the contract, please enter the correct contract name!')); process.exit(1); } @@ -108,15 +129,14 @@ function getMethod(method, contract) { } /** - * @description prompt contract address three times at most - * @param {*} { - * times, - * prompt, - * processAfterPrompt, // a function that will process user's input with first param as the raw input value of user - * pattern // the regular expression to validate the user's input - * } - * @param {Object} oraInstance the instance of ora library - * @return {Object} the correct input value, if no correct was inputted, it will throw an error then exit the process + * Prompts with tolerance for multiple attempts. + * @param {Object} options - Prompt options. + * @param {Function} options.processAfterPrompt - Function to process after prompt. + * @param {string | RegExp} options.pattern - Pattern for the prompt. + * @param {number} options.times - Number of times to prompt. + * @param {DistinctQuestion} options.prompt - prompt. + * @param {Ora} oraInstance - The ora instance for logging. + * @returns {Promise>} The result of the prompt. */ async function promptTolerateSeveralTimes({ processAfterPrompt = () => {}, pattern, times = 3, prompt = [] }, oraInstance) { if (pattern && !isRegExp(pattern)) { @@ -130,8 +150,8 @@ async function promptTolerateSeveralTimes({ processAfterPrompt = () => {}, patte while (askTimes < times) { try { answerInput = await inquirer.prompt(prompt); - answerInput = await processAfterPrompt(answerInput); + // @ts-ignore if (!pattern || pattern.test(answerInput)) { break; } @@ -142,6 +162,7 @@ async function promptTolerateSeveralTimes({ processAfterPrompt = () => {}, patte } } if (askTimes >= times && answerInput === null) { + // @ts-ignore oraInstance.fail(plainLogger.fatal(`You has entered wrong message ${times} times!`)); process.exit(1); } @@ -161,13 +182,24 @@ function isFilePath(val) { } } +/** + * Retrieves the result of a transaction. + * @param {*} aelf - The AElf instance. + * @param {string} txId - The transaction ID. + * @param {number} [times] - Number of times to retry. + * @param {number} [delay] - Delay between retries. + * @param {number} [timeLimit] - Time limit for retries. + * @returns {Promise} The transaction result. + */ async function getTxResult(aelf, txId, times = 0, delay = 3000, timeLimit = 3) { const currentTime = times + 1; - await new Promise(resolve => { - setTimeout(() => { - resolve(); - }, delay); - }); + await /** @type {Promise} */ ( + new Promise(resolve => { + setTimeout(() => { + resolve(); + }, delay); + }) + ); const tx = await aelf.chain.getTxResult(txId); if (tx.Status === 'PENDING' && currentTime <= timeLimit) { const result = await getTxResult(aelf, txId, currentTime, delay, timeLimit); @@ -182,6 +214,11 @@ async function getTxResult(aelf, txId, times = 0, delay = 3000, timeLimit = 3) { throw tx; } +/** + * Parses a JSON string. + * @param {string} [str] - The JSON string to parse. + * @returns {*} The parsed JSON object. + */ function parseJSON(str = '') { let result = null; try { @@ -192,6 +229,10 @@ function parseJSON(str = '') { return result; } +/** + * Generates a random ID. + * @returns {string} The random ID. + */ function randomId() { return uuid().replace(/-/g, ''); } @@ -261,6 +302,11 @@ async function getParamValue(type, fieldName) { return value; } +/** + * Retrieves parameters of a method. + * @param {*} method - The method. + * @returns {Promise>} A promise that resolves to the parameters object. + */ async function getParams(method) { const fields = Object.entries(method.inputTypeInfo.fields || {}); let result = {}; @@ -272,6 +318,9 @@ async function getParams(method) { ); console.log('Enter the params one by one, type `Enter` to skip optional param:'); if (isSpecialParameters(method.inputType)) { + /** + * @type {Object.} + */ let prompts = PROTO_TYPE_PROMPT_TYPE.default; prompts = { ...prompts, @@ -300,6 +349,9 @@ async function getParams(method) { const innerInputTypeInfo = innerType.toJSON(); const innerFields = Object.entries(innerInputTypeInfo.fields || {}); if (isSpecialParameters(innerFields)) { + /** + * @type {Object.} + */ let prompts = PROTO_TYPE_PROMPT_TYPE.default; prompts = { ...prompts, @@ -357,7 +409,12 @@ function getDeserializeLogResult(serializedData, dataType) { deserializeLogResult = AElf.utils.transform.transformArrayToMap(dataType, deserializeLogResult); return deserializeLogResult; } - +/** + * Deserializes logs from AElf. + * @param {*} aelf - The AElf instance. + * @param {Array} [logs] - The logs array to deserialize. + * @returns {Promise} A promise that resolves to the deserialized logs. + */ async function deserializeLogs(aelf, logs = []) { if (!logs || logs.length === 0) { return null; @@ -373,6 +430,7 @@ async function deserializeLogs(aelf, logs = []) { if (Name === 'VirtualTransactionCreated') { // VirtualTransactionCreated is system-default try { + // @ts-ignore const dataType = Root.VirtualTransactionCreated; return getDeserializeLogResult(serializedData, dataType); } catch (e) { diff --git a/src/utils/wallet.js b/src/utils/wallet.js index 7ebd37e..9aba7f8 100644 --- a/src/utils/wallet.js +++ b/src/utils/wallet.js @@ -1,7 +1,3 @@ -/** - * @file get wallet instance - * @author atom-yang - */ import AElf from 'aelf-sdk'; import fs from 'fs'; import path from 'path'; @@ -11,6 +7,13 @@ import Registry from '../rc/index.js'; import { passwordPrompts } from './constants.js'; import BaseSubCommand from '../command/baseSubCommand.js'; +/** + * Retrieves a wallet based on the provided command root, address, and password. + * @param {string} commandRoot - The root directory of the command. + * @param {string} address - The address of the wallet. + * @param {string} password - The password for the wallet. + * @returns {any} - The wallet instance. + */ function getWallet(commandRoot, address, password) { const keyStoreFile = path.resolve(commandRoot, `keys/${address}.json`); const keyStore = JSON.parse(Registry.getFileOrNot(keyStoreFile, '{}').toString()); @@ -25,6 +28,13 @@ function getWallet(commandRoot, address, password) { } } +/** + * Saves the wallet keystore to the specified directory. + * @param {any} wallet - The wallet instance to be saved. + * @param {string} datadir - The directory to save the wallet keystore. + * @param {string} [cipher] - Optional cipher to be used for encryption. + * @returns {Promise} - A promise that resolves to the path of the saved keystore file. + */ async function saveKeyStore(wallet, datadir, cipher = 'aes-128-ctr') { const { password, confirmPassword } = BaseSubCommand.normalizeConfig(await inquirer.prompt(passwordPrompts)); if (password !== confirmPassword) {