feat: integrate Sensei Spook mascot in 4 stances, speech bubbles, and style updates
This commit is contained in:
commit
e04672db2a
104 files changed
+8262
No files matched your search
@@ -0,0 +1,58 @@
|
||||
interface DeviceUsage {
|
||||
count: number;
|
||||
firstUsedAt: number;
|
||||
}
|
||||
|
||||
// In-memory usage map (or keyed by hashed client token)
|
||||
const usageStore = new Map<string, DeviceUsage>();
|
||||
|
||||
export interface RateLimitResult {
|
||||
allowed: boolean;
|
||||
remaining: number;
|
||||
resetMinutes: number;
|
||||
}
|
||||
|
||||
export function checkRateLimit(
|
||||
clientId: string,
|
||||
maxAllowed: number,
|
||||
cooldownMinutes: number
|
||||
): RateLimitResult {
|
||||
if (!maxAllowed || maxAllowed <= 0) {
|
||||
return { allowed: true, remaining: 999999, resetMinutes: 0 };
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const cooldownMs = cooldownMinutes * 60 * 1000;
|
||||
const usage = usageStore.get(clientId);
|
||||
|
||||
if (!usage) {
|
||||
return { allowed: true, remaining: maxAllowed, resetMinutes: 0 };
|
||||
}
|
||||
|
||||
// Check if cooldown period has elapsed since first use
|
||||
if (now - usage.firstUsedAt > cooldownMs) {
|
||||
usageStore.delete(clientId);
|
||||
return { allowed: true, remaining: maxAllowed, resetMinutes: 0 };
|
||||
}
|
||||
|
||||
const remaining = Math.max(0, maxAllowed - usage.count);
|
||||
const remainingCooldownMs = cooldownMs - (now - usage.firstUsedAt);
|
||||
const resetMinutes = Math.ceil(remainingCooldownMs / (60 * 1000));
|
||||
|
||||
return {
|
||||
allowed: usage.count < maxAllowed,
|
||||
remaining,
|
||||
resetMinutes,
|
||||
};
|
||||
}
|
||||
|
||||
export function incrementRateLimit(clientId: string): void {
|
||||
const now = Date.now();
|
||||
const usage = usageStore.get(clientId);
|
||||
|
||||
if (!usage) {
|
||||
usageStore.set(clientId, { count: 1, firstUsedAt: now });
|
||||
} else {
|
||||
usage.count += 1;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user