UNPKG

dt-common-device

Version:

A secure and robust device management library for IoT applications

168 lines (167 loc) 7.44 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.QueueUtils = void 0; const rateLimit_utils_1 = require("./rateLimit.utils"); const axios_1 = __importDefault(require("axios")); const dt_audit_library_1 = require("dt-audit-library"); const config_1 = require("../../config/config"); const redis_1 = require("../../db/redis"); class QueueUtils { static getQueueKey(microservice, connectionId, provider) { return `${microservice}_${provider}_${connectionId}`; } static getRequestQueueKey(connectionId, provider) { return `${connectionId}_${provider}`; } static getOrCreateQueue(queueKey, queues) { return (queues.get(queueKey) ?? queues .set(queueKey, new (require("bullmq").Queue)(queueKey, { connection: (0, redis_1.getRedisClient)(), })) .get(queueKey)); } static getOrCreateWorker(queueKey, workers, processFunction, jobResults) { if (workers.has(queueKey)) return; const { Worker } = require("bullmq"); const worker = new Worker(queueKey, processFunction, { connection: (0, redis_1.getRedisClient)(), concurrency: 1, removeOnComplete: true, removeOnFail: true, lockDuration: 300000, stalledInterval: 60000, }); // Event handlers for job tracking worker.on("completed", (job) => { (0, config_1.getConfig)().LOGGER.info(`HTTP request completed: ${job.id} [${queueKey}]`); const result = job.returnvalue; jobResults.set(job.id, { result, resolved: true, timestamp: Date.now(), }); }); worker.on("failed", (job, err) => { (0, config_1.getConfig)().LOGGER.error(`HTTP request failed: ${job?.id} [${queueKey}], Error: ${err.message}`); jobResults.set(job.id, { error: err.message, resolved: true, timestamp: Date.now(), }); }); worker.on("error", (err) => (0, config_1.getConfig)().LOGGER.error(`Worker error for ${queueKey}: ${err.message}`)); workers.set(queueKey, worker); (0, config_1.getConfig)().LOGGER.info(`Worker initialized for queue: ${queueKey}`); } static async waitForRateLimitExpiry(connectionId, provider, rateLimitConfigs) { const key = `rate_limit:${provider}:${connectionId}`; const config = rateLimitConfigs.get(provider); if (!config) return; while (true) { const timestamps = await rateLimit_utils_1.RateLimitUtils.getRawRequestTimestamps(key); const now = Date.now(); const windowStart = now - config.windowMs; const recentRequests = timestamps.filter((t) => t > windowStart); if (recentRequests.length < config.maxRequests) { // Rate limit not exceeded, we can proceed break; } // Calculate when the earliest request will expire const earliestRequest = recentRequests[0]; const nextAvailableTime = earliestRequest + config.windowMs; const delay = Math.max(nextAvailableTime - now, 1000); // At least 1 second (0, config_1.getConfig)().LOGGER.info(`Rate limit exceeded for ${provider} [${connectionId}]. Waiting ${delay}ms until next allowed request. Current requests in window: ${recentRequests.length}/${config.maxRequests}`); // Wait for the calculated delay await new Promise((resolve) => setTimeout(resolve, delay)); } } static async executeHttpRequest(url, method, options, connectionId, provider) { (0, config_1.getConfig)().LOGGER.info(`Executing: ${method} ${url} -> ${provider} [${connectionId}]`); try { // Record the request first await rateLimit_utils_1.RateLimitUtils.recordRequest(connectionId, provider); // Execute the HTTP request const response = await (0, axios_1.default)({ method: method.toLowerCase(), url: url, headers: options.headers || {}, timeout: 60000, ...(options.body && { data: options.body }), ...(options.params && { params: options.params }), }); (0, config_1.getConfig)().LOGGER.info(`HTTP request successful: ${method} ${url} for ${provider} [${connectionId}]`); // Return only the response data return response.data; } catch (error) { (0, config_1.getConfig)().LOGGER.error(`HTTP request failed: ${error.message}`); await (0, dt_audit_library_1.publishAudit)({ eventType: "http.request.error", properties: { connectionId, provider, endpoint: url, method, timestamp: Date.now(), reason: "execution_error", errorMessage: error.message, error: error, }, }); // Throw the error instead of returning it throw error; } } static async addJobToQueue(queueKey, jobData, delay, queues) { const queue = this.getOrCreateQueue(queueKey, queues); const job = await queue.add("http-request", jobData, { delay, attempts: 1, removeOnComplete: { age: 300, count: 1 }, removeOnFail: { age: 300, count: 1 }, }); return job.id; } static async waitForJobCompletion(jobId, queueKey, jobResults) { return new Promise((resolve, reject) => { let timeoutId; let checkCount = 0; const maxChecks = 600; const checkJob = async () => { if (++checkCount >= maxChecks) { clearTimeout(timeoutId); return reject(new Error("Job timeout: Request took too long")); } try { const memoryResult = jobResults.get(jobId); if (memoryResult?.resolved) { clearTimeout(timeoutId); return memoryResult.result !== undefined ? resolve(memoryResult.result) : reject(new Error(memoryResult.error || "Unknown error")); } // Continue checking timeoutId = setTimeout(checkJob, checkCount < 10 ? 50 : 100); } catch (error) { clearTimeout(timeoutId); reject(new Error(`Error checking job: ${error.message}`)); } }; checkJob(); const waitTime = queueKey.includes("cloudbeds") ? 180000 : 60000; // Backup timeout setTimeout(() => { clearTimeout(timeoutId); reject(new Error("Request timeout: Maximum wait time exceeded")); }, waitTime); }); } } exports.QueueUtils = QueueUtils;