From aef6deb89bbef9148809a10cd91cdb9f1bd9b9ee Mon Sep 17 00:00:00 2001 From: mareanalitica Date: Fri, 6 Oct 2023 06:41:30 -0300 Subject: [PATCH] docs:Update code comments, add JSDoc annotations --- src/libs/amqp.server.ts | 23 ++ src/libs/db.connect.ts | 54 ++++- src/libs/redis.client.ts | 49 ++++ src/libs/socket.server.ts | 26 ++- src/main.ts | 26 ++- src/utils/server-up.ts | 20 +- src/utils/use-multi-file-auth-state-db.ts | 14 +- .../use-multi-file-auth-state-redis-db.ts | 10 +- src/validate/validate.schema.ts | 19 +- src/whatsapp/services/auth.service.ts | 48 +++- src/whatsapp/services/monitor.service.ts | 210 ++++++++++++------ src/whatsapp/types/wa.types.ts | 53 ++++- 12 files changed, 471 insertions(+), 81 deletions(-) diff --git a/src/libs/amqp.server.ts b/src/libs/amqp.server.ts index 18b82c52..e625611c 100644 --- a/src/libs/amqp.server.ts +++ b/src/libs/amqp.server.ts @@ -3,19 +3,28 @@ import * as amqp from 'amqplib/callback_api'; import { configService, Rabbitmq } from '../config/env.config'; import { Logger } from '../config/logger.config'; +// Create a logger instance specifically for AMQP-related logs. const logger = new Logger('AMQP'); +// Declare an AMQP channel, initially set to null. let amqpChannel: amqp.Channel | null = null; +/** + * Initializes the AMQP (Advanced Message Queuing Protocol) connection. + * @returns {Promise} A promise that resolves when the AMQP connection is established. + */ export const initAMQP = () => { return new Promise((resolve, reject) => { const uri = configService.get('RABBITMQ').URI; + + // Connect to the RabbitMQ server. amqp.connect(uri, (error, connection) => { if (error) { reject(error); return; } + // Create an AMQP channel for communication. connection.createChannel((channelError, channel) => { if (channelError) { reject(channelError); @@ -24,6 +33,7 @@ export const initAMQP = () => { const exchangeName = 'evolution_exchange'; + // Declare an exchange with topic routing. channel.assertExchange(exchangeName, 'topic', { durable: true, autoDelete: false, @@ -38,13 +48,23 @@ export const initAMQP = () => { }); }; +/** + * Get the initialized AMQP channel. + * @returns {amqp.Channel | null} The initialized AMQP channel or null if not initialized. + */ export const getAMQP = (): amqp.Channel | null => { return amqpChannel; }; +/** + * Initializes queues for specified events. + * @param {string} instanceName - The name of the instance. + * @param {string[]} events - An array of event names. + */ export const initQueues = (instanceName: string, events: string[]) => { if (!events || !events.length) return; + // Transform event names into queue names. const queues = events.map((event) => { return `${event.replace(/_/g, '.').toLowerCase()}`; }); @@ -53,6 +73,7 @@ export const initQueues = (instanceName: string, events: string[]) => { const amqp = getAMQP(); const exchangeName = instanceName ?? 'evolution_exchange'; + // Assert the exchange with topic routing. amqp.assertExchange(exchangeName, 'topic', { durable: true, autoDelete: false, @@ -60,6 +81,7 @@ export const initQueues = (instanceName: string, events: string[]) => { const queueName = `${instanceName}.${event}`; + // Assert a queue with quorum support. amqp.assertQueue(queueName, { durable: true, autoDelete: false, @@ -68,6 +90,7 @@ export const initQueues = (instanceName: string, events: string[]) => { }, }); + // Bind the queue to the exchange with the corresponding event. amqp.bindQueue(queueName, exchangeName, event); }); }; diff --git a/src/libs/db.connect.ts b/src/libs/db.connect.ts index b11610c7..3730e0ab 100644 --- a/src/libs/db.connect.ts +++ b/src/libs/db.connect.ts @@ -3,23 +3,73 @@ import mongoose from 'mongoose'; import { configService, Database } from '../config/env.config'; import { Logger } from '../config/logger.config'; +/** + * Object for logging MongoDB-related messages. + * @type {Logger} + */ const logger = new Logger('MongoDB'); +/** + * Database settings retrieved from the configuration file. + * @type {Database} + */ const db = configService.get('DATABASE'); + +/** + * Function that creates and returns a connection to MongoDB using Mongoose. + * @returns {mongoose.Connection | undefined} MongoDB connection or `undefined` if the connection is not enabled. + */ export const dbserver = (() => { if (db.ENABLED) { + /** + * Log message indicating an attempt to connect to MongoDB. + */ logger.verbose('connecting'); - const dbs = mongoose.createConnection(db.CONNECTION.URI, { + + /** + * Options for the MongoDB connection. + * @type {mongoose.ConnectionOptions} + */ + const connectionOptions = { dbName: db.CONNECTION.DB_PREFIX_NAME + '-whatsapp-api', - }); + }; + + /** + * Creation of the MongoDB connection. + * @type {mongoose.Connection} + */ + const dbs = mongoose.createConnection(db.CONNECTION.URI, connectionOptions); + + /** + * Log message indicating the successful connection to MongoDB. + */ logger.verbose('connected in ' + db.CONNECTION.URI); + + /** + * Informative log message about the connected database name. + */ logger.info('ON - dbName: ' + dbs['$dbName']); + /** + * Registers an event handler for the beforeExit process event. + */ process.on('beforeExit', () => { + /** + * Log message indicating the destruction of the MongoDB connection instance. + */ logger.verbose('instance destroyed'); + + /** + * Destroys the MongoDB connection. + * @param {boolean} [force=false] - Indicates whether the destruction should be forced. + * @param {function(Error)} [callback] - Callback function to handle errors during destruction. + */ dbserver.destroy(true, (error) => logger.error(error)); }); + /** + * Returns the MongoDB connection. + */ return dbs; } })(); diff --git a/src/libs/redis.client.ts b/src/libs/redis.client.ts index f03513ba..848f4466 100644 --- a/src/libs/redis.client.ts +++ b/src/libs/redis.client.ts @@ -4,13 +4,24 @@ import { BufferJSON } from '@whiskeysockets/baileys'; import { Redis } from '../config/env.config'; import { Logger } from '../config/logger.config'; +/** + * Class representing a Redis cache. + */ export class RedisCache { + /** + * Disconnects from the Redis server. + */ async disconnect() { await this.client.disconnect(); this.statusConnection = false; } + + /** + * Creates a new instance of RedisCache. + */ constructor() { this.logger.verbose('instance created'); + process.on('beforeExit', async () => { this.logger.verbose('instance destroyed'); if (this.statusConnection) { @@ -24,11 +35,19 @@ export class RedisCache { private instanceName: string; private redisEnv: Redis; + /** + * Sets the reference for the Redis instance. + * @param {string} reference - The reference to set. + */ public set reference(reference: string) { this.logger.verbose('set reference: ' + reference); this.instanceName = reference; } + /** + * Connects to the Redis server. + * @param {Redis} redisEnv - The Redis configuration. + */ public async connect(redisEnv: Redis) { this.logger.verbose('connecting'); this.client = createClient({ url: redisEnv.URI }); @@ -41,6 +60,10 @@ export class RedisCache { private readonly logger = new Logger(RedisCache.name); private client: RedisClientType; + /** + * Retrieves keys for the Redis instance. + * @returns {Promise} An array of keys. + */ public async instanceKeys(): Promise { try { this.logger.verbose('instance keys: ' + this.redisEnv.PREFIX_KEY + ':*'); @@ -50,6 +73,11 @@ export class RedisCache { } } + /** + * Checks if a specific key exists. + * @param {string} key - The key to check. + * @returns {Promise} `true` if the key exists, otherwise `false`. + */ public async keyExists(key?: string) { if (key) { this.logger.verbose('keyExists: ' + key); @@ -59,6 +87,12 @@ export class RedisCache { return !!(await this.instanceKeys()).find((i) => i === this.instanceName); } + /** + * Writes data to Redis cache. + * @param {string} field - The field to write data to. + * @param {any} data - The data to write. + * @returns {Promise} `true` if the write is successful, otherwise `false`. + */ public async writeData(field: string, data: any) { try { this.logger.verbose('writeData: ' + field); @@ -70,6 +104,11 @@ export class RedisCache { } } + /** + * Reads data from Redis cache. + * @param {string} field - The field to read data from. + * @returns {Promise} The data if found, otherwise `null`. + */ public async readData(field: string) { try { this.logger.verbose('readData: ' + field); @@ -87,6 +126,11 @@ export class RedisCache { } } + /** + * Removes data from Redis cache. + * @param {string} field - The field to remove data from. + * @returns {Promise} `true` if the removal is successful, otherwise `false`. + */ public async removeData(field: string) { try { this.logger.verbose('removeData: ' + field); @@ -96,6 +140,11 @@ export class RedisCache { } } + /** + * Deletes all data associated with the Redis instance. + * @param {string} hash - The hash to delete, defaults to the instance name. + * @returns {Promise} `true` if the deletion is successful, otherwise `false`. + */ public async delAll(hash?: string) { try { this.logger.verbose('instance delAll: ' + hash); diff --git a/src/libs/socket.server.ts b/src/libs/socket.server.ts index ecf01ab1..2be50e95 100644 --- a/src/libs/socket.server.ts +++ b/src/libs/socket.server.ts @@ -4,23 +4,35 @@ import { Server as SocketIO } from 'socket.io'; import { configService, Cors, Websocket } from '../config/env.config'; import { Logger } from '../config/logger.config'; +// Create a logger instance specifically for socket-related logs. const logger = new Logger('Socket'); +// Declare the Socket.IO instance. let io: SocketIO; -const cors = configService.get('CORS').ORIGIN; +// Get the allowed origins from the configuration. +const corsOrigins = configService.get('CORS').ORIGIN; -export const initIO = (httpServer: Server) => { +/** + * Initialize Socket.IO with the provided HTTP server. + * @param {Server} httpServer - The HTTP server to attach Socket.IO to. + * @returns {SocketIO | null} The Socket.IO instance if enabled, or null if disabled. + */ +export const initIO = (httpServer: Server): SocketIO | null => { + // Check if WebSocket is enabled in the configuration. if (configService.get('WEBSOCKET')?.ENABLED) { + // Create a new Socket.IO instance with CORS configuration. io = new SocketIO(httpServer, { cors: { - origin: cors, + origin: corsOrigins, }, }); + // Handle the 'connection' event when a user connects. io.on('connection', (socket) => { logger.info('User connected'); + // Handle the 'disconnect' event when a user disconnects. socket.on('disconnect', () => { logger.info('User disconnected'); }); @@ -29,12 +41,20 @@ export const initIO = (httpServer: Server) => { logger.info('Socket.io initialized'); return io; } + + // WebSocket is disabled, return null. return null; }; +/** + * Get the Socket.IO instance. + * @throws {Error} Throws an error if Socket.IO is not initialized. + * @returns {SocketIO} The initialized Socket.IO instance. + */ export const getIO = (): SocketIO => { logger.verbose('Getting Socket.io'); + // If Socket.IO is not initialized, throw an error. if (!io) { logger.error('Socket.io not initialized'); throw new Error('Socket.io not initialized'); diff --git a/src/main.ts b/src/main.ts index e239f83a..a320f6d7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -16,16 +16,27 @@ import { ServerUP } from './utils/server-up'; import { HttpStatus, router } from './whatsapp/routers/index.router'; import { waMonitor } from './whatsapp/whatsapp.module'; +/** + * Initializes WhatsApp monitoring. + */ function initWA() { waMonitor.loadInstance(); } +/** + * Bootstrap the application. + */ function bootstrap() { const logger = new Logger('SERVER'); const app = express(); app.use( cors({ + /** + * Determine if the request origin is allowed by CORS. + * @param requestOrigin The origin of the incoming request. + * @param callback The callback function to invoke. + */ origin(requestOrigin, callback) { const { ORIGIN } = configService.get('CORS'); if (ORIGIN.includes('*')) { @@ -53,12 +64,19 @@ function bootstrap() { app.use('/', router); app.use( + /** + * Error handling middleware. + * @param err The error object. + * @param req The Express request object. + * @param res The Express response object. + * @param next The next function to invoke. + */ (err: Error, req: Request, res: Response, next: NextFunction) => { if (err) { const webhook = configService.get('WEBHOOK'); if (webhook.EVENTS.ERRORS_WEBHOOK && webhook.EVENTS.ERRORS_WEBHOOK != '' && webhook.EVENTS.ERRORS) { - const tzoffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds + const tzoffset = new Date().getTimezoneOffset() * 60000; // offset in milliseconds const localISOTime = new Date(Date.now() - tzoffset).toISOString(); const now = localISOTime; const globalApiKey = configService.get('AUTHENTICATION').API_KEY.KEY; @@ -98,6 +116,12 @@ function bootstrap() { next(); }, + /** + * 404 Not Found middleware. + * @param req The Express request object. + * @param res The Express response object. + * @param next The next function to invoke. + */ (req: Request, res: Response, next: NextFunction) => { const { method, url } = req; diff --git a/src/utils/server-up.ts b/src/utils/server-up.ts index e06caea7..bccde362 100644 --- a/src/utils/server-up.ts +++ b/src/utils/server-up.ts @@ -8,22 +8,38 @@ import { configService, SslConf } from '../config/env.config'; export class ServerUP { static #app: Express; + /** + * Set the Express application instance. + * @param {Express} e - The Express application instance. + */ static set app(e: Express) { this.#app = e; } + /** + * Get an HTTPS server instance with SSL configuration. + * @returns {https.Server} An HTTPS server instance. + */ static get https() { + // Retrieve SSL certificate and private key paths from configuration. const { FULLCHAIN, PRIVKEY } = configService.get('SSL_CONF'); + + // Create an HTTPS server using the SSL certificate and private key. return https.createServer( { - cert: readFileSync(FULLCHAIN), - key: readFileSync(PRIVKEY), + cert: readFileSync(FULLCHAIN), // Read SSL certificate file. + key: readFileSync(PRIVKEY), // Read private key file. }, ServerUP.#app, ); } + /** + * Get an HTTP server instance. + * @returns {http.Server} An HTTP server instance. + */ static get http() { + // Create an HTTP server using the Express application instance. return http.createServer(ServerUP.#app); } } diff --git a/src/utils/use-multi-file-auth-state-db.ts b/src/utils/use-multi-file-auth-state-db.ts index a021372a..d7175a7f 100644 --- a/src/utils/use-multi-file-auth-state-db.ts +++ b/src/utils/use-multi-file-auth-state-db.ts @@ -11,17 +11,25 @@ import { configService, Database } from '../config/env.config'; import { Logger } from '../config/logger.config'; import { dbserver } from '../libs/db.connect'; +/** + * Provides a function to handle AuthenticationState and credentials using a MongoDB collection. + * @param {string} coll - The name of the MongoDB collection. + * @returns {Promise<{ state: AuthenticationState; saveCreds: () => Promise }>} An object with AuthenticationState and saveCreds function. + */ export async function useMultiFileAuthStateDb( coll: string, ): Promise<{ state: AuthenticationState; saveCreds: () => Promise }> { const logger = new Logger(useMultiFileAuthStateDb.name); + // Get the MongoDB client from the database server connection. const client = dbserver.getClient(); + // Construct the collection name based on the database prefix. const collection = client .db(configService.get('DATABASE').CONNECTION.DB_PREFIX_NAME + '-instances') .collection(coll); + // Helper function to write data to the MongoDB collection. const writeData = async (data: any, key: string): Promise => { try { await client.connect(); @@ -33,6 +41,7 @@ export async function useMultiFileAuthStateDb( } }; + // Helper function to read data from the MongoDB collection. const readData = async (key: string): Promise => { try { await client.connect(); @@ -44,6 +53,7 @@ export async function useMultiFileAuthStateDb( } }; + // Helper function to remove data from the MongoDB collection. const removeData = async (key: string) => { try { await client.connect(); @@ -53,6 +63,7 @@ export async function useMultiFileAuthStateDb( } }; + // Initialize AuthenticationCreds using stored or default values. const creds: AuthenticationCreds = (await readData('creds')) || initAuthCreds(); return { @@ -60,8 +71,6 @@ export async function useMultiFileAuthStateDb( creds, keys: { get: async (type, ids: string[]) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore const data: { [_: string]: SignalDataTypeMap[type] } = {}; await Promise.all( ids.map(async (id) => { @@ -90,6 +99,7 @@ export async function useMultiFileAuthStateDb( }, }, }, + // Save the credentials to the MongoDB collection. saveCreds: async () => { return writeData(creds, 'creds'); }, diff --git a/src/utils/use-multi-file-auth-state-redis-db.ts b/src/utils/use-multi-file-auth-state-redis-db.ts index 8e685b54..459c1958 100644 --- a/src/utils/use-multi-file-auth-state-redis-db.ts +++ b/src/utils/use-multi-file-auth-state-redis-db.ts @@ -9,6 +9,11 @@ import { import { Logger } from '../config/logger.config'; import { RedisCache } from '../libs/redis.client'; +/** + * Provides a function to handle AuthenticationState and credentials using a Redis cache. + * @param {RedisCache} cache - The RedisCache instance to store and retrieve data. + * @returns {Promise<{ state: AuthenticationState; saveCreds: () => Promise }>} An object with AuthenticationState and saveCreds function. + */ export async function useMultiFileAuthStateRedisDb(cache: RedisCache): Promise<{ state: AuthenticationState; saveCreds: () => Promise; @@ -27,11 +32,12 @@ export async function useMultiFileAuthStateRedisDb(cache: RedisCache): Promise<{ try { return await cache.readData(key); } catch (error) { - logger.error({ readData: 'writeData', error }); + logger.error({ readData: 'readData', error }); return; } }; + // Helper function to remove data from the Redis cache. const removeData = async (key: string) => { try { return await cache.removeData(key); @@ -40,6 +46,7 @@ export async function useMultiFileAuthStateRedisDb(cache: RedisCache): Promise<{ } }; + // Initialize AuthenticationCreds using stored or default values. const creds: AuthenticationCreds = (await readData('creds')) || initAuthCreds(); return { @@ -77,6 +84,7 @@ export async function useMultiFileAuthStateRedisDb(cache: RedisCache): Promise<{ }, }, }, + // Save the credentials to the Redis cache. saveCreds: async () => { return await writeData(creds, 'creds'); }, diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index b8a4c0ad..129e172f 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -1,14 +1,20 @@ import { JSONSchema7, JSONSchema7Definition } from 'json-schema'; import { v4 } from 'uuid'; +/** + * Represents a utility function to generate JSON schema for non-empty properties. + * + * @param {string[]} propertyNames - The names of properties to check for non-emptiness. + * @returns {JSONSchema7} A JSON schema with non-empty property checks. + */ const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => { const properties = {}; propertyNames.forEach( (property) => - (properties[property] = { - minLength: 1, - description: `The "${property}" cannot be empty`, - }), + (properties[property] = { + minLength: 1, + description: `The "${property}" cannot be empty`, + }), ); return { if: { @@ -21,6 +27,11 @@ const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => { }; // Instance Schema +/** + * JSON schema for an instance with properties like instanceName, webhook, and more. + * + * @type {JSONSchema7} + */ export const instanceNameSchema: JSONSchema7 = { $id: v4(), type: 'object', diff --git a/src/whatsapp/services/auth.service.ts b/src/whatsapp/services/auth.service.ts index 915a07b7..0debcb04 100644 --- a/src/whatsapp/services/auth.service.ts +++ b/src/whatsapp/services/auth.service.ts @@ -11,6 +11,9 @@ import { InstanceDto } from '../dto/instance.dto'; import { RepositoryBroker } from '../repository/repository.manager'; import { WAMonitoringService } from './monitor.service'; +/** + * Represents the payload of a JWT token. + */ export type JwtPayload = { instanceName: string; apiName: string; @@ -19,19 +22,36 @@ export type JwtPayload = { tokenId: string; }; +/** + * Represents the structure of an old JWT token. + */ export class OldToken { oldToken: string; } - +/** + * Service responsible for authentication-related operations. + */ export class AuthService { + /** + * Creates an instance of AuthService. + * @param configService The configuration service. + * @param waMonitor The monitoring service for WhatsApp instances. + * @param repository The repository manager for database operations. + */ constructor( private readonly configService: ConfigService, private readonly waMonitor: WAMonitoringService, private readonly repository: RepositoryBroker, - ) {} + ) { } private readonly logger = new Logger(AuthService.name); + /** + * Generates a JWT token for a given instance. + * @param instance The instance DTO for which to generate the token. + * @returns An object containing the generated JWT token. + * @throws BadRequestException if an error occurs during token generation. + */ private async jwt(instance: InstanceDto) { const jwtOpts = this.configService.get('AUTHENTICATION').JWT; const token = sign( @@ -61,6 +81,13 @@ export class AuthService { return { jwt: token }; } + /** + * Generates an API key for a given instance. + * @param instance The instance DTO for which to generate the API key. + * @param token (Optional) An existing API key to use. + * @returns An object containing the generated or defined API key. + * @throws BadRequestException if an error occurs during API key generation. + */ private async apikey(instance: InstanceDto, token?: string) { const apikey = token ? token : v4().toUpperCase(); @@ -81,6 +108,11 @@ export class AuthService { return { apikey }; } + /** + * Checks for the existence of a duplicate token among instances. + * @param token The token to check for duplication. + * @returns `true` if the token is not duplicated among instances, otherwise throws a BadRequestException. + */ public async checkDuplicateToken(token: string) { const instances = await this.waMonitor.instanceInfo(); @@ -97,6 +129,12 @@ export class AuthService { return true; } + /** + * Generates an authentication hash (JWT token or API key) based on the authentication type. + * @param instance The instance DTO for which to generate the hash. + * @param token (Optional) An existing token to use (for API key generation). + * @returns An object containing the generated authentication hash (JWT token or API key). + */ public async generateHash(instance: InstanceDto, token?: string) { const options = this.configService.get('AUTHENTICATION'); @@ -105,6 +143,12 @@ export class AuthService { return (await this[options.TYPE](instance, token)) as { jwt: string } | { apikey: string }; } + /** + * Refreshes a JWT token based on an old JWT token. + * @param oldToken An old JWT token to refresh. + * @returns An object containing the refreshed JWT token and instanceName. + * @throws BadRequestException if the oldToken is invalid or an error occurs during token refresh. + */ public async refreshToken({ oldToken }: OldToken) { this.logger.verbose('refreshing token'); diff --git a/src/whatsapp/services/monitor.service.ts b/src/whatsapp/services/monitor.service.ts index c7574968..f0752855 100644 --- a/src/whatsapp/services/monitor.service.ts +++ b/src/whatsapp/services/monitor.service.ts @@ -24,41 +24,95 @@ import { import { RepositoryBroker } from '../repository/repository.manager'; import { WAStartupService } from './whatsapp.service'; +/** + * Represents a service for monitoring WhatsApp instances. + */ export class WAMonitoringService { + /** + * Creates an instance of WAMonitoringService. + * @param {EventEmitter2} eventEmitter - Event emitter for handling events. + * @param {ConfigService} configService - Configuration service. + * @param {RepositoryBroker} repository - Repository broker for database operations. + * @param {RedisCache} cache - Redis cache for storing data. + */ constructor( private readonly eventEmitter: EventEmitter2, private readonly configService: ConfigService, private readonly repository: RepositoryBroker, private readonly cache: RedisCache, ) { + /** + * Logger instance for logging service activities. + * @private + * @type {Logger} + */ this.logger.verbose('instance created'); + // Initialize service functionalities this.removeInstance(); this.noConnection(); this.delInstanceFiles(); + // Load configuration settings Object.assign(this.db, configService.get('DATABASE')); Object.assign(this.redis, configService.get('REDIS')); + // Initialize the MongoDB database instance this.dbInstance = this.db.ENABLED ? this.repository.dbServer?.db(this.db.CONNECTION.DB_PREFIX_NAME + '-instances') : undefined; } + /** + * Configuration settings for the database. + * @private + * @type {Partial} + */ private readonly db: Partial = {}; + + /** + * Configuration settings for Redis. + * @private + * @type {Partial} + */ private readonly redis: Partial = {}; + /** + * Database instance for instance management. + * @private + * @type {Db} + */ private dbInstance: Db; + /** + * Database store connection. + * @private + * @type {Db} + */ private dbStore = dbserver; + /** + * Logger instance for logging service activities. + * @private + * @type {Logger} + */ private readonly logger = new Logger(WAMonitoringService.name); + + /** + * A dictionary of WhatsApp instances being monitored. + * @public + * @type {Record} + */ public readonly waInstances: Record = {}; + /** + * Initiates a timer to remove an instance after a specific time of inactivity. + * @param {string} instance - The name of the instance to monitor. + */ public delInstanceTime(instance: string) { const time = this.configService.get('DEL_INSTANCE'); if (typeof time === 'number' && time > 0) { - this.logger.verbose(`Instance "${instance}" don't have connection, will be removed in ${time} minutes`); + this.logger.verbose(`Instance "${instance}" doesn't have a connection, will be removed in ${time} minutes`); setTimeout(async () => { if (this.waInstances[instance]?.connectionStatus?.state !== 'open') { @@ -75,67 +129,74 @@ export class WAMonitoringService { }, 1000 * 60 * time); } } -/* ocultado por francis inicio - public async instanceInfo(instanceName?: string) { - this.logger.verbose('get instance info'); - - const urlServer = this.configService.get('SERVER').URL; - - const instances: any[] = await Promise.all( - Object.entries(this.waInstances).map(async ([key, value]) => { - const status = value?.connectionStatus?.state || 'unknown'; - - if (status === 'unknown') { - return null; - } - - if (status === 'open') { - this.logger.verbose('instance: ' + key + ' - connectionStatus: open'); - } - - const instanceData: any = { - instance: { - instanceName: key, - owner: value.wuid, - profileName: (await value.getProfileName()) || 'not loaded', - profilePictureUrl: value.profilePictureUrl, - profileStatus: (await value.getProfileStatus()) || '', - status: status, - }, - }; - - if (this.configService.get('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) { - instanceData.instance.serverUrl = urlServer; - instanceData.instance.apikey = (await this.repository.auth.find(key))?.apikey; - - const findChatwoot = await this.waInstances[key].findChatwoot(); - if (findChatwoot && findChatwoot.enabled) { - instanceData.instance.chatwoot = { - ...findChatwoot, - webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(key)}`, - }; + /* ocultado por francis inicio + public async instanceInfo(instanceName?: string) { + this.logger.verbose('get instance info'); + + const urlServer = this.configService.get('SERVER').URL; + + const instances: any[] = await Promise.all( + Object.entries(this.waInstances).map(async ([key, value]) => { + const status = value?.connectionStatus?.state || 'unknown'; + + if (status === 'unknown') { + return null; } - } - - return instanceData; - }), - ).then((results) => results.filter((instance) => instance !== null)); - - this.logger.verbose('return instance info: ' + instances.length); - - if (instanceName) { - const instance = instances.find((i) => i.instance.instanceName === instanceName); - return instance || []; + + if (status === 'open') { + this.logger.verbose('instance: ' + key + ' - connectionStatus: open'); + } + + const instanceData: any = { + instance: { + instanceName: key, + owner: value.wuid, + profileName: (await value.getProfileName()) || 'not loaded', + profilePictureUrl: value.profilePictureUrl, + profileStatus: (await value.getProfileStatus()) || '', + status: status, + }, + }; + + if (this.configService.get('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) { + instanceData.instance.serverUrl = urlServer; + instanceData.instance.apikey = (await this.repository.auth.find(key))?.apikey; + + const findChatwoot = await this.waInstances[key].findChatwoot(); + if (findChatwoot && findChatwoot.enabled) { + instanceData.instance.chatwoot = { + ...findChatwoot, + webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(key)}`, + }; + } + } + + return instanceData; + }), + ).then((results) => results.filter((instance) => instance !== null)); + + this.logger.verbose('return instance info: ' + instances.length); + + if (instanceName) { + const instance = instances.find((i) => i.instance.instanceName === instanceName); + return instance || []; + } + + return instances; } + + ocultado por francis fim */ - return instances; - } + // inserido por francis inicio -ocultado por francis fim */ -// inserido por francis inicio - -public async instanceInfo(instanceName?: string) { + /** + * Retrieves information about WhatsApp instances, including details about the connection, owner, and status. + * @param instanceName The name of the WhatsApp instance to retrieve information for. Optional. + * @returns An array of objects containing information about WhatsApp instances. + * @throws NotFoundException If `instanceName` is specified, and the instance is not found. + */ + public async instanceInfo(instanceName?: string) { this.logger.verbose('get instance info'); if (instanceName && !this.waInstances[instanceName]) { throw new NotFoundException(`Instance "${instanceName}" not found`); @@ -212,8 +273,7 @@ public async instanceInfo(instanceName?: string) { -// inserido por francis fim - + // inserido por francis fim @@ -221,6 +281,9 @@ public async instanceInfo(instanceName?: string) { + /** + * Initializes a cron job to delete instance files at regular intervals. + */ private delInstanceFiles() { this.logger.verbose('cron to delete instance files started'); setInterval(async () => { @@ -255,6 +318,10 @@ public async instanceInfo(instanceName?: string) { }, 3600 * 1000 * 2); } + /** + * Cleans data for a specific WhatsApp instance, including the database, cache, or files, depending on the configuration. + * @param instanceName The name of the WhatsApp instance to clean up. + */ public async cleaningUp(instanceName: string) { this.logger.verbose('cleaning up instance: ' + instanceName); if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) { @@ -278,6 +345,11 @@ public async instanceInfo(instanceName?: string) { rmSync(join(INSTANCE_DIR, instanceName), { recursive: true, force: true }); } + /** + * Cleans storage files for a specific WhatsApp instance, including messages, contacts, etc. + * + * @param instanceName The name of the WhatsApp instance to clean storage files for. + */ public async cleaningStoreFiles(instanceName: string) { if (!this.db.ENABLED) { this.logger.verbose('cleaning store files instance: ' + instanceName); @@ -314,7 +386,9 @@ public async instanceInfo(instanceName?: string) { return; } - + /** + * Loads WhatsApp instances based on storage settings, such as Redis, a database, or local files. + */ public async loadInstance() { this.logger.verbose('Loading instances'); @@ -330,7 +404,11 @@ public async instanceInfo(instanceName?: string) { this.logger.error(error); } } - + /** + * Configures a new WhatsApp instance and connects it to the WhatsApp service. + * + * @param name The name of the WhatsApp instance to configure. + */ private async setInstance(name: string) { const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache); instance.instanceName = name; @@ -394,7 +472,11 @@ public async instanceInfo(instanceName?: string) { }), ); } - + /** + * Removes a WhatsApp instance from memory and performs associated data cleanup. + * + * @param instanceName The name of the WhatsApp instance to remove. + */ private removeInstance() { this.eventEmitter.on('remove.instance', async (instanceName: string) => { this.logger.verbose('remove instance: ' + instanceName); @@ -423,7 +505,9 @@ public async instanceInfo(instanceName?: string) { } }); } - + /** + * Checks for WhatsApp instances without a connection and takes appropriate actions, such as logout and connection closure. + */ private noConnection() { this.logger.verbose('checking instances without connection'); this.eventEmitter.on('no.connection', async (instanceName) => { diff --git a/src/whatsapp/types/wa.types.ts b/src/whatsapp/types/wa.types.ts index 2025e7f7..5cda691b 100644 --- a/src/whatsapp/types/wa.types.ts +++ b/src/whatsapp/types/wa.types.ts @@ -1,6 +1,8 @@ /* eslint-disable @typescript-eslint/no-namespace */ -import { AuthenticationState, WAConnectionState } from '@whiskeysockets/baileys'; +/** + * Enumeration of various application events. + */ export enum Events { APPLICATION_STARTUP = 'application.startup', QRCODE_UPDATED = 'qrcode.updated', @@ -28,13 +30,23 @@ export enum Events { CHAMA_AI_ACTION = 'chama-ai.action', } +/** + * Namespace containing various WhatsApp-related types. + */ export declare namespace wa { + /** + * Represents a QR code for pairing with WhatsApp. + */ export type QrCode = { count?: number; pairingCode?: string; base64?: string; code?: string; }; + + /** + * Represents information about a WhatsApp instance. + */ export type Instance = { qrcode?: QrCode; pairingCode?: string; @@ -45,6 +57,9 @@ export declare namespace wa { profilePictureUrl?: string; }; + /** + * Represents local webhook settings. + */ export type LocalWebHook = { enabled?: boolean; url?: string; @@ -52,6 +67,9 @@ export declare namespace wa { webhook_by_events?: boolean; }; + /** + * Represents local Chatwoot settings. + */ export type LocalChatwoot = { enabled?: boolean; account_id?: string; @@ -64,6 +82,9 @@ export declare namespace wa { conversation_pending?: boolean; }; + /** + * Represents local settings. + */ export type LocalSettings = { reject_call?: boolean; msg_call?: string; @@ -73,22 +94,34 @@ export declare namespace wa { read_status?: boolean; }; + /** + * Represents local WebSocket settings. + */ export type LocalWebsocket = { enabled?: boolean; events?: string[]; }; + /** + * Represents local RabbitMQ settings. + */ export type LocalRabbitmq = { enabled?: boolean; events?: string[]; }; + /** + * Represents a session within a Typebot instance. + */ type Session = { remoteJid?: string; sessionId?: string; createdAt?: number; }; + /** + * Represents local Typebot settings. + */ export type LocalTypebot = { enabled?: boolean; url?: string; @@ -101,11 +134,17 @@ export declare namespace wa { sessions?: Session[]; }; + /** + * Represents local proxy settings. + */ export type LocalProxy = { enabled?: boolean; proxy?: string; }; + /** + * Represents local Chamaai settings. + */ export type LocalChamaai = { enabled?: boolean; url?: string; @@ -114,17 +153,29 @@ export declare namespace wa { answerByAudio?: boolean; }; + /** + * Represents the state of a connection with a WhatsApp instance. + */ export type StateConnection = { instance?: string; state?: WAConnectionState | 'refused'; statusReason?: number; }; + /** + * Represents a status message type. + */ export type StatusMessage = 'ERROR' | 'PENDING' | 'SERVER_ACK' | 'DELIVERY_ACK' | 'READ' | 'DELETED' | 'PLAYED'; } +/** + * Array of media message types. + */ export const TypeMediaMessage = ['imageMessage', 'documentMessage', 'audioMessage', 'videoMessage', 'stickerMessage']; +/** + * Array of message subtype types. + */ export const MessageSubtype = [ 'ephemeralMessage', 'documentWithCaptionMessage',