⏳ RateLimit

Globalny rate limiter dla akcji w aplikacji

Opis

Moduł RateLimit kontroluje częstotliwość wykonywania akcji w aplikacji. Obsługuje różne strategie limitowania (sliding window, token bucket) i integruje się z Timer.cooldown oraz SecureStorage dla persystencji.

Podstawowe użycie

// Sprawdź i wykonaj
if (ADict.RateLimit.tryAcquire("api_call")) {
    makeApiCall()
} else {
    showRateLimitedMessage()
}

// Z automatyczną akcją
ADict.RateLimit.tryAcquire("share_button", cooldownMs = 5000L) {
    shareContent()
}

// Z fallbackiem
val result = ADict.RateLimit.withLimit("search", fallback = emptyList()) {
    api.search(query)
}

Konfiguracja limitów

// Sliding window - 60 req/min
ADict.RateLimit.configure("api_call") {
    maxRequests = 60
    windowMinutes(1)
    strategy = Strategy.SLIDING_WINDOW
}

// Token bucket - burst + sustained rate
ADict.RateLimit.configure("ad_request") {
    maxRequests = 10
    windowMs = 60_000L
    strategy = Strategy.TOKEN_BUCKET
    burstSize = 3
    refillRatePerSecond = 0.5
}

// Z blokowaniem po przekroczeniu
ADict.RateLimit.configure("login_attempt") {
    maxRequests = 5
    windowMinutes(15)
    blockDurationMs = 30_000L  // Zablokuj na 30s
}

Strategie

Strategia Opis Użycie
FIXED_WINDOW Stałe okno czasowe Proste limity, np. 100/godz
SLIDING_WINDOW Przesuwne okno (domyślna) Płynne limity bez spike'ów
TOKEN_BUCKET Burst + sustained rate API z burstami, reklamy
LEAKY_BUCKET Stały output rate Równomierny przepływ

Presety

// Użyj gotowych presetów
ADict.RateLimit.applyPreset(RateLimit.Presets.API_STANDARD)      // 60/min
ADict.RateLimit.applyPreset(RateLimit.Presets.API_STRICT)        // 10/min + block
ADict.RateLimit.applyPreset(RateLimit.Presets.BUTTON_ANTISPAM)   // 5/10s
ADict.RateLimit.applyPreset(RateLimit.Presets.AD_REQUEST)        // Token bucket
ADict.RateLimit.applyPreset(RateLimit.Presets.SHARE)             // 10/5min

Statystyki

val stats = ADict.RateLimit.getStats("api_call")
println("Użyto: ${stats?.currentCount}/${stats?.maxRequests}")
println("Pozostało: ${stats?.remainingRequests}")
println("Reset za: ${stats?.resetInMs}ms")
println("Zablokowano: ${stats?.blockedCount} razy")

// Quick checks
val remaining = ADict.RateLimit.getRemaining("api_call")
val isLimited = ADict.RateLimit.isLimited("api_call")
val resetTime = ADict.RateLimit.getResetTime("api_call")

Callbacks

ADict.RateLimit.onLimitReached { id, stats ->
    analytics.log("rate_limited", mapOf(
        "action" to id,
        "blocked_count" to stats.blockedCount
    ))

    if (id == "api_call") {
        showApiLimitWarning()
    }
}

Remote Config

// Dynamiczne limity z Remote Config
// Format w RC: "maxRequests,windowMs,strategy"
// np. "100,60000,SLIDING_WINDOW"
ADict.RateLimit.configureFromRemote(
    id = "api_call",
    remoteKey = "rate_limit_api",
    defaultConfig = RateLimit.Presets.API_STANDARD
)

Reset

// Reset pojedynczego limitu
ADict.RateLimit.reset("api_call")

// Reset wszystkich
ADict.RateLimit.resetAll()

Debounce

Executes action only after delay with no new calls. Each new call resets the timer.

// In search field - wait for user to stop typing
searchField.addTextChangedListener { text ->
    ADict.RateLimit.debounce("search", 300L) {
        performSearch(text.toString())
    }
}

// Cancel pending debounce
ADict.RateLimit.cancelDebounce("search")

Throttle

Executes action at most once per interval. First call executes immediately.

// Button throttle - max once per second
button.setOnClickListener {
    ADict.RateLimit.throttle("save_button", 1000L) {
        saveData()
    }
}

// Throttle with trailing edge - executes last call after interval
scrollView.setOnScrollChangeListener { _, _, _, _, _ ->
    ADict.RateLimit.throttleTrailing("scroll_analytics", 500L) {
        logScrollPosition()
    }
}

// Reset throttle (allow immediate execution)
ADict.RateLimit.resetThrottle("save_button")

View Extensions

import rip.nerd.adictlibrary.modules.setOnClickDebounced
import rip.nerd.adictlibrary.modules.setOnClickThrottled
import rip.nerd.adictlibrary.modules.setOnClickRateLimited

// Debounced click - prevents double clicks
button.setOnClickDebounced(500L) {
    navigateToNextScreen()
}

// Throttled click - max once per interval
likeButton.setOnClickThrottled(1000L) {
    toggleLike()
}

// Rate limited click with callback
shareButton.setOnClickRateLimited(
    limitId = "share_action",
    maxClicks = 3,
    windowMs = 10_000L,
    onLimitReached = { showTooManySharesMessage() }
) {
    shareContent()
}