diff --git a/api/rate_limit_config.py b/api/rate_limit_config.py index 0f56375..091e5eb 100644 --- a/api/rate_limit_config.py +++ b/api/rate_limit_config.py @@ -27,8 +27,8 @@ class RateLimitConfig: "/api/listing_geojson": EndpointLimit(10, 60), "/api/listing_geojson/stream": EndpointLimit(10, 60), "/api/refresh_listings": EndpointLimit(3, 300), - "/api/task_status": EndpointLimit(60, 60), - "/api/tasks_for_user": EndpointLimit(30, 60), + "/api/task_status": EndpointLimit(200, 60), + "/api/tasks_for_user": EndpointLimit(60, 60), "/api/cancel_task": EndpointLimit(10, 60), "/api/clear_all_tasks": EndpointLimit(5, 60), "/api/get_districts": EndpointLimit(20, 60), @@ -87,8 +87,8 @@ class RateLimitConfig: "/api/listing_geojson": _parse_limit("RATE_LIMIT_GEOJSON", 10, 60), "/api/listing_geojson/stream": _parse_limit("RATE_LIMIT_GEOJSON_STREAM", 10, 60), "/api/refresh_listings": _parse_limit("RATE_LIMIT_REFRESH", 3, 300), - "/api/task_status": _parse_limit("RATE_LIMIT_TASK_STATUS", 60, 60), - "/api/tasks_for_user": _parse_limit("RATE_LIMIT_TASKS_FOR_USER", 30, 60), + "/api/task_status": _parse_limit("RATE_LIMIT_TASK_STATUS", 200, 60), + "/api/tasks_for_user": _parse_limit("RATE_LIMIT_TASKS_FOR_USER", 60, 60), "/api/cancel_task": _parse_limit("RATE_LIMIT_CANCEL_TASK", 10, 60), "/api/clear_all_tasks": _parse_limit("RATE_LIMIT_CLEAR_TASKS", 5, 60), "/api/get_districts": _parse_limit("RATE_LIMIT_DISTRICTS", 20, 60), diff --git a/frontend/src/hooks/useTaskProgress.ts b/frontend/src/hooks/useTaskProgress.ts index 0282210..6606c8b 100644 --- a/frontend/src/hooks/useTaskProgress.ts +++ b/frontend/src/hooks/useTaskProgress.ts @@ -1,11 +1,13 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import type { AuthUser } from '@/auth/types'; import type { TaskState, TaskStatusResponse, WSMessage } from '@/types'; -import { WS_TASKS_PATH, POLLING_INTERVALS } from '@/constants'; +import { WS_TASKS_PATH } from '@/constants'; import { fetchTasksForUser, fetchTaskStatus } from '@/services'; const KEEPALIVE_MS = 30_000; const MAX_RECONNECT_DELAY_MS = 30_000; +const POLL_FAST_MS = 5_000; // WS down — polling is the primary update source +const POLL_SLOW_MS = 30_000; // WS up — polling is just a safety net function wsUrl(token: string): string { const proto = window.location.protocol === 'https:' ? 'wss' : 'ws'; @@ -116,9 +118,15 @@ export function useTaskProgress(user: AuthUser | null): UseTaskProgressReturn { } }, []); - const startPolling = useCallback(() => { + const startPolling = useCallback((intervalMs: number = POLL_SLOW_MS) => { const currentUser = userRef.current; - if (!currentUser || pollingTimer.current) return; + if (!currentUser) return; + + // Stop existing polling before (re)starting with the requested interval + if (pollingTimer.current) { + clearInterval(pollingTimer.current); + pollingTimer.current = null; + } const fetchAndPoll = async () => { const u = userRef.current; @@ -157,7 +165,7 @@ export function useTaskProgress(user: AuthUser | null): UseTaskProgressReturn { }; fetchAndPoll(); - pollingTimer.current = setInterval(fetchAndPoll, POLLING_INTERVALS.TASK_STATUS_MS); + pollingTimer.current = setInterval(fetchAndPoll, intervalMs); }, []); // No reactive deps — reads from refs // ---- WebSocket connection ---- @@ -173,6 +181,9 @@ export function useTaskProgress(user: AuthUser | null): UseTaskProgressReturn { setIsConnected(true); reconnectAttempt.current = 0; + // WS is primary — slow down polling to safety-net rate + startPolling(POLL_SLOW_MS); + // Start keepalive pings keepaliveTimer.current = setInterval(() => { if (ws.readyState === WebSocket.OPEN) { @@ -219,8 +230,8 @@ export function useTaskProgress(user: AuthUser | null): UseTaskProgressReturn { keepaliveTimer.current = null; } - // Fallback to polling while WS is down - startPolling(); + // WS is down — poll at fast rate as primary update source + startPolling(POLL_FAST_MS); // Exponential backoff reconnect const delay = Math.min(