feat: add advanced analytics and territory management system
- Add comprehensive analytics components with export functionality - Implement territory management with manager performance tracking - Add seatmap components for venue layout management - Create customer management features with modal interface - Add advanced hooks for dashboard flags and territory data - Implement seat selection and venue management utilities - Add type definitions for ticketing and seatmap systems 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
265
reactrebuild0825/src/features/scanner/RateLimiter.ts
Normal file
265
reactrebuild0825/src/features/scanner/RateLimiter.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
/**
|
||||
* Rate Limiter for Scanner Abuse Prevention
|
||||
*
|
||||
* Implements sliding window rate limiting to prevent scan spam
|
||||
* and ensure stable gate operations.
|
||||
*/
|
||||
|
||||
export interface RateLimitResult {
|
||||
allowed: boolean;
|
||||
waitTime?: number; // milliseconds until next scan allowed
|
||||
message?: string;
|
||||
currentRate: number; // scans per second in current window
|
||||
}
|
||||
|
||||
export interface RateLimiterConfig {
|
||||
maxScansPerSecond: number;
|
||||
windowSizeMs: number;
|
||||
cooldownMs: number; // Additional cooldown when limit exceeded
|
||||
}
|
||||
|
||||
export class ScannerRateLimiter {
|
||||
private scanTimestamps: number[] = [];
|
||||
private lastViolationTime: number | null = null;
|
||||
private violationCount = 0;
|
||||
private readonly config: RateLimiterConfig;
|
||||
|
||||
constructor(config: Partial<RateLimiterConfig> = {}) {
|
||||
this.config = {
|
||||
maxScansPerSecond: 8,
|
||||
windowSizeMs: 1000,
|
||||
cooldownMs: 2000,
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a scan is allowed at the current time
|
||||
*/
|
||||
checkRate(now = Date.now()): RateLimitResult {
|
||||
this.cleanOldTimestamps(now);
|
||||
|
||||
const currentRate = this.getCurrentRate(now);
|
||||
const inCooldown = this.isInCooldown(now);
|
||||
|
||||
if (inCooldown) {
|
||||
const remainingCooldown = this.getRemainingCooldown(now);
|
||||
return {
|
||||
allowed: false,
|
||||
waitTime: remainingCooldown,
|
||||
message: `Cooling down - wait ${Math.ceil(remainingCooldown / 1000)}s`,
|
||||
currentRate,
|
||||
};
|
||||
}
|
||||
|
||||
if (currentRate >= this.config.maxScansPerSecond) {
|
||||
this.recordViolation(now);
|
||||
return {
|
||||
allowed: false,
|
||||
waitTime: this.config.cooldownMs,
|
||||
message: 'Scanning too fast - slow down',
|
||||
currentRate,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
allowed: true,
|
||||
currentRate,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a successful scan
|
||||
*/
|
||||
recordScan(now = Date.now()): void {
|
||||
this.scanTimestamps.push(now);
|
||||
this.cleanOldTimestamps(now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current scans per second rate
|
||||
*/
|
||||
getScansInWindow(now = Date.now()): number {
|
||||
this.cleanOldTimestamps(now);
|
||||
return this.scanTimestamps.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current rate as scans per second
|
||||
*/
|
||||
private getCurrentRate(now: number): number {
|
||||
const scansInWindow = this.getScansInWindow(now);
|
||||
return scansInWindow * (1000 / this.config.windowSizeMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove timestamps outside the sliding window
|
||||
*/
|
||||
private cleanOldTimestamps(now: number): void {
|
||||
const cutoff = now - this.config.windowSizeMs;
|
||||
this.scanTimestamps = this.scanTimestamps.filter(time => time > cutoff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if currently in cooldown period
|
||||
*/
|
||||
private isInCooldown(now: number): boolean {
|
||||
if (!this.lastViolationTime) {return false;}
|
||||
return (now - this.lastViolationTime) < this.config.cooldownMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remaining cooldown time in milliseconds
|
||||
*/
|
||||
private getRemainingCooldown(now: number): number {
|
||||
if (!this.lastViolationTime) {return 0;}
|
||||
const elapsed = now - this.lastViolationTime;
|
||||
return Math.max(0, this.config.cooldownMs - elapsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a rate limit violation
|
||||
*/
|
||||
private recordViolation(now: number): void {
|
||||
this.lastViolationTime = now;
|
||||
this.violationCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get statistics for monitoring
|
||||
*/
|
||||
getStats(now = Date.now()): {
|
||||
currentRate: number;
|
||||
scansInWindow: number;
|
||||
violationCount: number;
|
||||
inCooldown: boolean;
|
||||
remainingCooldown: number;
|
||||
} {
|
||||
return {
|
||||
currentRate: this.getCurrentRate(now),
|
||||
scansInWindow: this.getScansInWindow(now),
|
||||
violationCount: this.violationCount,
|
||||
inCooldown: this.isInCooldown(now),
|
||||
remainingCooldown: this.getRemainingCooldown(now),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset rate limiter state (for testing or manual reset)
|
||||
*/
|
||||
reset(): void {
|
||||
this.scanTimestamps = [];
|
||||
this.lastViolationTime = null;
|
||||
this.violationCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Device-level abuse tracker
|
||||
* Tracks suspicious patterns and implements exponential backoff
|
||||
*/
|
||||
export class DeviceAbuseTracker {
|
||||
private violations = 0;
|
||||
private lastViolationTime: number | null = null;
|
||||
private suspiciousPatterns = 0;
|
||||
private readonly deviceFingerprint: string;
|
||||
|
||||
constructor() {
|
||||
this.deviceFingerprint = this.generateFingerprint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate device fingerprint for tracking
|
||||
*/
|
||||
private generateFingerprint(): string {
|
||||
const components = [
|
||||
navigator.userAgent.split(' ').slice(0, 3).join('_'), // Simplified user agent
|
||||
`${screen.width }x${ screen.height}`,
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
navigator.language,
|
||||
];
|
||||
|
||||
// Simple hash of components
|
||||
let hash = 0;
|
||||
const str = components.join('|');
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash; // Convert to 32bit integer
|
||||
}
|
||||
|
||||
return Math.abs(hash).toString(36);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a rate limit violation
|
||||
*/
|
||||
recordViolation(now = Date.now()): void {
|
||||
this.violations++;
|
||||
this.lastViolationTime = now;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for suspicious patterns and apply exponential backoff
|
||||
*/
|
||||
checkAbuseStatus(now = Date.now()): {
|
||||
isAbusive: boolean;
|
||||
backoffTime: number;
|
||||
message?: string;
|
||||
} {
|
||||
// No violations recorded
|
||||
if (this.violations === 0) {
|
||||
return { isAbusive: false, backoffTime: 0 };
|
||||
}
|
||||
|
||||
// Calculate exponential backoff based on violation count
|
||||
const baseBackoff = 5000; // 5 seconds base backoff
|
||||
const backoffMultiplier = Math.pow(2, Math.min(this.violations - 1, 6)); // Cap at 2^6 = 64x
|
||||
const backoffTime = baseBackoff * backoffMultiplier;
|
||||
|
||||
// Check if still in backoff period
|
||||
if (this.lastViolationTime && (now - this.lastViolationTime) < backoffTime) {
|
||||
const remainingTime = backoffTime - (now - this.lastViolationTime);
|
||||
return {
|
||||
isAbusive: true,
|
||||
backoffTime: remainingTime,
|
||||
message: `Device blocked - wait ${Math.ceil(remainingTime / 1000)}s`,
|
||||
};
|
||||
}
|
||||
|
||||
return { isAbusive: false, backoffTime: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* Record suspicious scanning pattern
|
||||
*/
|
||||
recordSuspiciousPattern(): void {
|
||||
this.suspiciousPatterns++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get device abuse statistics
|
||||
*/
|
||||
getStats(): {
|
||||
deviceId: string;
|
||||
violations: number;
|
||||
suspiciousPatterns: number;
|
||||
lastViolation: number | null;
|
||||
} {
|
||||
return {
|
||||
deviceId: this.deviceFingerprint,
|
||||
violations: this.violations,
|
||||
suspiciousPatterns: this.suspiciousPatterns,
|
||||
lastViolation: this.lastViolationTime,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset abuse tracking (for testing or manual reset)
|
||||
*/
|
||||
reset(): void {
|
||||
this.violations = 0;
|
||||
this.lastViolationTime = null;
|
||||
this.suspiciousPatterns = 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user