( ′∀`)σ≡σ☆))Д′)レ(゚∀゚;)ヘ=З=З=Зε≡(ノ´_ゝ`)ノ
// Rate Limiter for Fabric.js Layer Modifications
class FabricRateLimiter {
constructor(options = {}) {
this.maxRequests = options.maxRequests || 5; // Max requests allowed
this.timeWindow = options.timeWindow || 10000; // Time window in ms (10 seconds)
this.cooldownPeriod = options.cooldownPeriod || 5000; // Cooldown after limit hit (5 seconds)
this.requests = [];
this.isLocked = false;
this.lockEndTime = null;
this.pendingOperation = null;
this.debounceTimer = null;
this.debounceDelay = options.debounceDelay || 500; // Debounce delay in ms
}
// Check if rate limit is exceeded
isRateLimited() {
const now = Date.now();
// If currently locked, check if cooldown period has passed
if (this.isLocked) {
if (now < this.lockEndTime) {
return {
limited: true,
error: 'Save rate limit exceeded. Please wait a moment.',
waitTime: Math.ceil((this.lockEndTime - now) / 1000)
};
} else {
// Cooldown period over, unlock
this.isLocked = false;
this.lockEndTime = null;
this.requests = [];
}
}
// Remove requests outside the time window
this.requests = this.requests.filter(time => now - time < this.timeWindow);
// Check if limit exceeded
if (this.requests.length >= this.maxRequests) {
this.isLocked = true;
this.lockEndTime = now + this.cooldownPeriod;
return {
limited: true,
error: 'Save rate limit exceeded. Please wait a moment.',
waitTime: Math.ceil(this.cooldownPeriod / 1000)
};
}
return { limited: false };
}
// Record a request
recordRequest() {
this.requests.push(Date.now());
}
// Attempt to execute an operation with rate limiting
async execute(operation) {
const check = this.isRateLimited();
if (check.limited) {
return {
success: false,
error: check.error,
waitTime: check.waitTime
};
}
this.recordRequest();
try {
const result = await operation();
return { success: true, data: result };
} catch (error) {
return { success: false, error: error.message };
}
}
// Debounced execution - only executes after user stops making changes
debounce(operation) {
clearTimeout(this.debounceTimer);
this.pendingOperation = operation;
return new Promise((resolve) => {
this.debounceTimer = setTimeout(async () => {
const result = await this.execute(this.pendingOperation);
resolve(result);
}, this.debounceDelay);
});
}
// Get current status
getStatus() {
const now = Date.now();
this.requests = this.requests.filter(time => now - time < this.timeWindow);
return {
isLocked: this.isLocked,
requestCount: this.requests.length,
maxRequests: this.maxRequests,
remainingRequests: Math.max(0, this.maxRequests - this.requests.length),
waitTime: this.isLocked ? Math.ceil((this.lockEndTime - now) / 1000) : 0
};
}
// Reset the limiter
reset() {
this.requests = [];
this.isLocked = false;
this.lockEndTime = null;
clearTimeout(this.debounceTimer);
}
}
// Example usage with Fabric.js
class FabricCanvasManager {
constructor(canvasId, options = {}) {
this.canvas = new fabric.Canvas(canvasId);
this.rateLimiter = new FabricRateLimiter(options);
this.setupEventListeners();
}
setupEventListeners() {
// Track object modifications
this.canvas.on('object:modified', () => {
this.handleModification();
});
// Track object additions
this.canvas.on('object:added', () => {
this.handleModification();
});
// Track object removals
this.canvas.on('object:removed', () => {
this.handleModification();
});
}
// Handle modifications with debouncing and rate limiting
async handleModification() {
const result = await this.rateLimiter.debounce(() => this.saveToServer());
if (!result.success) {
this.showError(result.error, result.waitTime);
} else {
this.showSuccess('Changes saved successfully');
}
}
// Simulate server save
async saveToServer() {
const canvasData = JSON.stringify(this.canvas.toJSON());
// Simulate API call
return new Promise((resolve) => {
setTimeout(() => {
console.log('Saving to server:', canvasData.substring(0, 100) + '...');
resolve({ saved: true, timestamp: Date.now() });
}, 300);
});
}
// Manual save with immediate rate limit check
async manualSave() {
const result = await this.rateLimiter.execute(() => this.saveToServer());
if (!result.success) {
this.showError(result.error, result.waitTime);
return false;
} else {
this.showSuccess('Changes saved successfully');
return true;
}
}
showError(message, waitTime) {
console.error(message, waitTime ? `Wait ${waitTime} seconds` : '');
// You can replace this with your UI notification system
alert(`${message}${waitTime ? ` Please wait ${waitTime} seconds.` : ''}`);
}
showSuccess(message) {
console.log(message);
// You can replace this with your UI notification system
}
getStatus() {
return this.rateLimiter.getStatus();
}
}
// Usage Example:
/*
const canvasManager = new FabricCanvasManager('myCanvas', {
maxRequests: 5, // Max 5 saves
timeWindow: 10000, // Within 10 seconds
cooldownPeriod: 5000, // 5 second cooldown if exceeded
debounceDelay: 500 // Wait 500ms after last change
});
// Check status
console.log(canvasManager.getStatus());
// Manual save
canvasManager.manualSave();
*/