n8n-nodes-duckduckgo-search
Version:
A powerful and comprehensive n8n community node that seamlessly integrates DuckDuckGo search capabilities into your workflows. Search the web, find images, discover news, and explore videos - all with privacy-focused, reliable results.
182 lines (181 loc) • 6.53 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.rateLimited = exports.getGlobalRateLimiter = exports.RateLimiter = exports.DEFAULT_RATE_LIMITS = void 0;
exports.DEFAULT_RATE_LIMITS = {
search: {
maxRequests: 30,
windowMs: 60000,
delayMs: 1000,
retryAfterMs: 30000,
burstAllowance: 5,
},
instantAnswer: {
maxRequests: 100,
windowMs: 60000,
delayMs: 500,
retryAfterMs: 15000,
burstAllowance: 10,
},
dictionary: {
maxRequests: 60,
windowMs: 60000,
delayMs: 500,
retryAfterMs: 15000,
burstAllowance: 5,
},
stocks: {
maxRequests: 20,
windowMs: 60000,
delayMs: 2000,
retryAfterMs: 60000,
burstAllowance: 3,
},
currency: {
maxRequests: 50,
windowMs: 60000,
delayMs: 500,
retryAfterMs: 20000,
burstAllowance: 5,
},
};
class RateLimiter {
constructor(config = exports.DEFAULT_RATE_LIMITS.search) {
this.config = config;
this.trackers = new Map();
this.globalTracker = {
count: 0,
firstRequestTime: 0,
lastRequestTime: 0,
isRateLimited: false,
};
}
async checkAndWait(operation, context, itemIndex) {
const now = Date.now();
const operationConfig = exports.DEFAULT_RATE_LIMITS[operation] || this.config;
let tracker = this.trackers.get(operation);
if (!tracker) {
tracker = {
count: 0,
firstRequestTime: now,
lastRequestTime: now,
isRateLimited: false,
};
this.trackers.set(operation, tracker);
}
if (tracker.isRateLimited && tracker.rateLimitedUntil) {
if (now < tracker.rateLimitedUntil) {
const waitTime = tracker.rateLimitedUntil - now;
if (context) {
context.logger.warn(`Rate limited for ${operation}. Waiting ${waitTime}ms before retry.`);
}
await this.sleep(waitTime);
tracker.isRateLimited = false;
tracker.rateLimitedUntil = undefined;
}
}
if (now - tracker.firstRequestTime > operationConfig.windowMs) {
tracker.count = 0;
tracker.firstRequestTime = now;
}
const effectiveLimit = operationConfig.maxRequests + (operationConfig.burstAllowance || 0);
if (tracker.count >= effectiveLimit) {
tracker.isRateLimited = true;
tracker.rateLimitedUntil = now + (operationConfig.retryAfterMs || 30000);
if (context) {
context.logger.error(`Rate limit exceeded for ${operation}. Max ${effectiveLimit} requests per ${operationConfig.windowMs}ms.`);
}
return false;
}
const timeSinceLastRequest = now - tracker.lastRequestTime;
const requiredDelay = operationConfig.delayMs || 1000;
if (timeSinceLastRequest < requiredDelay && tracker.count > 0) {
const waitTime = requiredDelay - timeSinceLastRequest;
if (context) {
context.logger.info(`Applying rate limit delay of ${waitTime}ms for ${operation}`);
}
await this.sleep(waitTime);
}
tracker.count++;
tracker.lastRequestTime = Date.now();
this.updateGlobalTracking();
return true;
}
updateGlobalTracking() {
const now = Date.now();
if (now - this.globalTracker.firstRequestTime > 60000) {
this.globalTracker.count = 0;
this.globalTracker.firstRequestTime = now;
}
this.globalTracker.count++;
this.globalTracker.lastRequestTime = now;
if (this.globalTracker.count > 100) {
this.globalTracker.isRateLimited = true;
this.globalTracker.rateLimitedUntil = now + 30000;
}
}
getStatus(operation) {
const tracker = this.trackers.get(operation);
const config = exports.DEFAULT_RATE_LIMITS[operation] || this.config;
if (!tracker) {
return {
remaining: config.maxRequests,
resetTime: 0,
isLimited: false,
};
}
const now = Date.now();
const windowEnd = tracker.firstRequestTime + config.windowMs;
const remaining = Math.max(0, config.maxRequests - tracker.count);
return {
remaining,
resetTime: windowEnd > now ? windowEnd : 0,
isLimited: tracker.isRateLimited || false,
};
}
reset(operation) {
if (operation) {
this.trackers.delete(operation);
}
else {
this.trackers.clear();
this.globalTracker = {
count: 0,
firstRequestTime: 0,
lastRequestTime: 0,
isRateLimited: false,
};
}
}
async applyBackoff(attemptNumber, baseDelayMs = 1000, maxDelayMs = 30000) {
const delay = Math.min(baseDelayMs * Math.pow(2, attemptNumber), maxDelayMs);
await this.sleep(delay);
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
exports.RateLimiter = RateLimiter;
let globalRateLimiter = null;
function getGlobalRateLimiter() {
if (!globalRateLimiter) {
globalRateLimiter = new RateLimiter();
}
return globalRateLimiter;
}
exports.getGlobalRateLimiter = getGlobalRateLimiter;
function rateLimited(operation) {
return function (target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args) {
const rateLimiter = getGlobalRateLimiter();
const context = args.find(arg => arg && typeof arg.logger === 'object');
const canProceed = await rateLimiter.checkAndWait(operation, context);
if (!canProceed) {
throw new Error(`Rate limit exceeded for ${operation}. Please try again later.`);
}
return originalMethod.apply(this, args);
};
return descriptor;
};
}
exports.rateLimited = rateLimited;