dt-common-device
Version:
A secure and robust device management library for IoT applications
168 lines (167 loc) • 7.44 kB
JavaScript
;
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;