UNPKG

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
"use strict"; 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;