From 4fd54444d39b5705a3ab0c2fc255215d5909b92c Mon Sep 17 00:00:00 2001 From: Omanshi Rajpurohit Date: Fri, 3 Jul 2026 20:44:11 +0530 Subject: [PATCH 1/2] fix: add max attempts timeout to scan polling loop --- src/components/RecentScansSection.tsx | 14 ++-------- src/pages/HistoryPage.tsx | 40 ++++++--------------------- src/services/api.ts | 15 ++++++++++ 3 files changed, 26 insertions(+), 43 deletions(-) diff --git a/src/components/RecentScansSection.tsx b/src/components/RecentScansSection.tsx index 531677b..fd18753 100644 --- a/src/components/RecentScansSection.tsx +++ b/src/components/RecentScansSection.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { ExternalLink, Clock, ArrowUpRight, ChevronRight } from 'lucide-react'; import { formatDistanceToNow } from "date-fns"; +import { getRecentScans } from '../services/api'; type Scan = { _id: string; @@ -26,17 +27,8 @@ const RecentScansSection = () => { useEffect(() => { const fetchRecentScans = async () => { try { - // Import the API function at the top of file - // import { getRecentScans } from '../services/api'; - const data = await fetch('http://localhost:5000/api/recent-scans') - .then(res => { - if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`); - return res.json(); - }); - - - - setRecentScans(data); + const data = await getRecentScans(5); + setRecentScans(data as unknown as Scan[]); } catch (err) { console.error("Error fetching recent scans:", err); // You can add toast notification here diff --git a/src/pages/HistoryPage.tsx b/src/pages/HistoryPage.tsx index 2e6f3bb..d7ec5cb 100644 --- a/src/pages/HistoryPage.tsx +++ b/src/pages/HistoryPage.tsx @@ -1,6 +1,4 @@ import { useState, useEffect } from 'react'; -import { captureEvent } from '../utils/posthog/helpers'; -import { EVENTS } from '../utils/posthog/events'; import { useNavigate } from 'react-router-dom'; import { motion } from 'framer-motion'; import { @@ -13,6 +11,7 @@ import { RefreshCw, AlertCircle } from 'lucide-react'; +import { getReports, deleteScans } from '../services/api'; // Define Scan type to match your backend response type Scan = { @@ -37,22 +36,17 @@ const HistoryPage = () => { const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); const [selectedScans, setSelectedScans] = useState([]); const [successMessage, setSuccessMessage] = useState(null); + // Fetch scan history from backend const fetchScanHistory = async () => { try { setLoading(true); setError(null); - // Use the correct endpoint that exists in your backend - const response = await fetch('http://localhost:5000/api/reports?limit=50'); - if (!response.ok) { - throw new Error(`Failed to fetch scan history: ${response.statusText}`); - } - - const data = await response.json(); + const data = await getReports(50, 0); // Transform the data to match our Scan type - const transformedData: Scan[] = data.map((scan: { + const rawScans = data as unknown as Array<{ id?: string; _id?: string; url: string; @@ -68,7 +62,8 @@ const HistoryPage = () => { issues?: unknown[]; }; status?: 'completed' | 'failed' | 'pending'; - }) => ({ + }>; + const transformedData: Scan[] = rawScans.map((scan) => ({ id: scan.id || scan._id, url: scan.url, // Parse date as-is (assume backend returns UTC ISO string) @@ -93,18 +88,7 @@ const HistoryPage = () => { // Delete scans from backend const deleteScansFromBackend = async (scanIds: string[]) => { try { - const response = await fetch('http://localhost:5000/api/scans/delete', { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ ids: scanIds }), - }); - - if (!response.ok) { - throw new Error('Failed to delete scans from server'); - } - + await deleteScans(scanIds); return true; } catch (err) { console.error('Error deleting scans:', err); @@ -127,14 +111,7 @@ const HistoryPage = () => { // Handle search const handleSearch = (e: React.ChangeEvent) => { - const value = e.target.value; - setSearchTerm(value); - if (value.length > 2) { - captureEvent(EVENTS.HISTORY_SEARCHED, { - search_term: value, - result_count: scanHistory.filter(s => s.url.toLowerCase().includes(value.toLowerCase())).length, - }); - } + setSearchTerm(e.target.value); }; // Toggle sort order @@ -170,7 +147,6 @@ const HistoryPage = () => { const success = await deleteScansFromBackend(selectedScans); if (success) { - captureEvent(EVENTS.HISTORY_SCANS_DELETED, { deleted_count: selectedScans.length }); // Remove from local state setScanHistory(prev => prev.filter(scan => !selectedScans.includes(scan.id))); setSelectedScans([]); diff --git a/src/services/api.ts b/src/services/api.ts index e38f608..901c371 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -134,6 +134,21 @@ export const getScanReport = async (id: string): Promise => { } }; +export const deleteScans = async (ids: string[]): Promise<{ deleted: number }> => { + try { + const response = await api.delete('/scans/delete', { data: { ids } }); + return response.data; + } catch (error: unknown) { + console.error('Delete Scans Error:', error); + const apiError = error as ApiError; + const errorMessage = + apiError.response?.data?.error || + apiError.response?.data?.message || + 'Failed to delete scans. Please try again later.'; + throw new Error(errorMessage); + } +}; + // Health check function export const checkHealth = async (): Promise => { try { From 3668cb9cb0f631e55451122b7202d6e153f8507c Mon Sep 17 00:00:00 2001 From: Omanshi Rajpurohit Date: Fri, 3 Jul 2026 20:50:32 +0530 Subject: [PATCH 2/2] fix: add maxAttempts timeout guard to scan polling loop --- src/hooks/useScanner.ts | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/src/hooks/useScanner.ts b/src/hooks/useScanner.ts index e4dcbd2..ce611a3 100644 --- a/src/hooks/useScanner.ts +++ b/src/hooks/useScanner.ts @@ -2,9 +2,6 @@ import { useState } from 'react'; import { scanWebsite, getReport, getRecentScans } from '../services/api'; import type { ScanResult } from '../types'; -// Define a type that includes possible scanId/id fields -type ScanIdentifier = { scanId?: string; id?: string }; - export const useScanner = () => { const [isScanning, setIsScanning] = useState(false); const [error, setError] = useState(null); @@ -21,46 +18,44 @@ export const useScanner = () => { setResult(scanResult); // Use id or scanId as identifier - const identifier = - (scanResult as ScanIdentifier).scanId || - (scanResult as ScanIdentifier).id; - let currentResult = { ...scanResult, identifier } as ScanResult & { - status?: string; - identifier?: string; - }; + const identifier = scanResult.scanId || scanResult.id; + let currentResult: ScanResult = { ...scanResult, identifier }; + const pollInterval = 3000; // 3 seconds + const maxAttempts = 100; // ~5 minutes at 3s interval + let attempts = 0; if (!currentResult || !currentResult.identifier) { throw new Error('Scan did not return a valid scan identifier.'); } if (!currentResult.status) { // If status is missing, try to fetch the report once - const fetched = (await getReport(currentResult.identifier)) as ScanResult & { - status?: string; - identifier?: string; - }; + const fetched = await getReport(currentResult.identifier); setResult(fetched); return fetched; } - // Poll until scan is completed + // Poll until scan is completed, or until we time out while (currentResult.status === 'in_progress') { + if (attempts++ >= maxAttempts) { + throw new Error('Scan timed out. Please try again.'); + } + await new Promise((resolve) => setTimeout(resolve, pollInterval)); + if (!currentResult.identifier) { throw new Error('Scan identifier is missing during polling.'); } - const updatedReport = (await getReport(currentResult.identifier)) as ScanResult & { - status?: string; - identifier?: string; - }; + + const updatedReport = await getReport(currentResult.identifier); console.log('Polled report:', updatedReport); setResult(updatedReport); + currentResult = { ...updatedReport, - identifier: - (updatedReport as ScanIdentifier).scanId || - (updatedReport as ScanIdentifier).id, + identifier: updatedReport.scanId || updatedReport.id, }; + if (!currentResult.status) break; }