node-red-contrib-tplink-tapo-connect-api
Version:
This unofficial node-RED node allows connection to TP-Link Tapo devices. This project has been enhanced with AI support to enable new features. Starting with v0.50, we have added support for the KLAP protocol. To prioritize the operation of this node, we
205 lines • 6.91 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RequestManager = exports.RequestPriority = void 0;
var RequestPriority;
(function (RequestPriority) {
RequestPriority[RequestPriority["LOW"] = 0] = "LOW";
RequestPriority[RequestPriority["NORMAL"] = 1] = "NORMAL";
RequestPriority[RequestPriority["HIGH"] = 2] = "HIGH";
RequestPriority[RequestPriority["CRITICAL"] = 3] = "CRITICAL";
})(RequestPriority || (exports.RequestPriority = RequestPriority = {}));
/**
* Manages request queuing, rate limiting, and request lifecycle
* Single responsibility: Request queue management and rate limiting
*/
class RequestManager {
constructor(minRequestInterval = 100) {
this.minRequestInterval = minRequestInterval;
this.requestQueue = [];
this.isProcessing = false;
this.lastRequestTime = 0;
this.requestCounter = 0;
}
/**
* Queue a request for execution
*/
async queueRequest(request, options = {}) {
return new Promise((resolve, reject) => {
var _a;
const queuedRequest = {
id: this.generateRequestId(),
request,
options: {
timeout: 10000,
retries: 3,
priority: RequestPriority.NORMAL,
...options
},
priority: (_a = options.priority) !== null && _a !== void 0 ? _a : RequestPriority.NORMAL,
timestamp: Date.now(),
resolve,
reject
};
this.addToQueue(queuedRequest);
this.processQueue();
});
}
/**
* Add request to queue with priority ordering
*/
addToQueue(request) {
// Insert request in priority order (higher priority first)
let insertIndex = this.requestQueue.length;
for (let i = 0; i < this.requestQueue.length; i++) {
const queuedItem = this.requestQueue[i];
if (queuedItem && queuedItem.priority < request.priority) {
insertIndex = i;
break;
}
}
this.requestQueue.splice(insertIndex, 0, request);
}
/**
* Process the request queue
*/
async processQueue() {
if (this.isProcessing || this.requestQueue.length === 0) {
return;
}
this.isProcessing = true;
try {
while (this.requestQueue.length > 0) {
const queuedRequest = this.requestQueue.shift();
if (!queuedRequest)
continue;
try {
// Enforce rate limiting
await this.enforceRateLimit();
// Execute the request
const response = await this.executeRequest(queuedRequest);
if (queuedRequest) {
queuedRequest.resolve(response);
}
}
catch (error) {
// Handle retries
if (this.shouldRetry(queuedRequest, error)) {
// Reduce retry count and re-queue
queuedRequest.options.retries--;
this.addToQueue(queuedRequest);
}
else {
queuedRequest.reject(error);
}
}
this.lastRequestTime = Date.now();
}
}
finally {
this.isProcessing = false;
}
}
/**
* Set the request executor function
*/
setRequestExecutor(executor) {
this.requestExecutor = executor;
}
/**
* Execute a single request using the configured executor
*/
async executeRequest(queuedRequest) {
if (!this.requestExecutor) {
throw new Error('Request executor not configured. Call setRequestExecutor() first.');
}
return this.requestExecutor(queuedRequest.request);
}
/**
* Enforce rate limiting between requests
*/
async enforceRateLimit() {
const timeSinceLastRequest = Date.now() - this.lastRequestTime;
if (timeSinceLastRequest < this.minRequestInterval) {
const delayTime = this.minRequestInterval - timeSinceLastRequest;
await new Promise(resolve => setTimeout(resolve, delayTime));
}
}
/**
* Determine if a request should be retried
*/
shouldRetry(queuedRequest, error) {
var _a;
const retriesLeft = (_a = queuedRequest.options.retries) !== null && _a !== void 0 ? _a : 0;
if (retriesLeft <= 0) {
return false;
}
// Don't retry certain types of errors
const nonRetryableErrors = [
'authentication failed',
'invalid credentials',
'device not found',
'permission denied'
];
const errorMessage = error.message.toLowerCase();
const isNonRetryable = nonRetryableErrors.some(pattern => errorMessage.includes(pattern));
return !isNonRetryable;
}
/**
* Generate unique request ID
*/
generateRequestId() {
return `req_${Date.now()}_${++this.requestCounter}`;
}
/**
* Get current queue status
*/
getQueueStatus() {
return {
queueLength: this.requestQueue.length,
isProcessing: this.isProcessing,
lastRequestTime: this.lastRequestTime
};
}
/**
* Clear the request queue (useful for cleanup)
*/
clearQueue() {
// Reject all pending requests
for (const queuedRequest of this.requestQueue) {
queuedRequest.reject(new Error('Request queue cleared'));
}
this.requestQueue = [];
this.isProcessing = false;
}
/**
* Cancel a specific request by ID
*/
cancelRequest(requestId) {
const index = this.requestQueue.findIndex(req => req.id === requestId);
if (index !== -1) {
const cancelledRequest = this.requestQueue.splice(index, 1)[0];
if (cancelledRequest) {
cancelledRequest.reject(new Error('Request cancelled'));
}
return true;
}
return false;
}
/**
* Get requests by priority
*/
getRequestsByPriority(priority) {
return this.requestQueue.filter(req => req.priority === priority);
}
/**
* Update minimum request interval (useful for different protocols)
*/
updateMinRequestInterval(interval) {
if (interval > 0) {
// Cast to access private field for updating
this.minRequestInterval = interval;
}
}
}
exports.RequestManager = RequestManager;
//# sourceMappingURL=request-manager.js.map