import { onMounted, onUnmounted, ref } from 'vue'; import { platformConfigs } from '@/config/platforms'; import type { CrawlTaskState } from '@/types'; import { sendBackgroundMessage } from '@/shared/message'; const CRAWL_TASK_STORAGE_KEY = 'crawlTaskState'; const ACTIVE_STATUSES = new Set(['running', 'paused']); export const useScan = () => { const selectedPlatformId = ref(platformConfigs[0]?.id ?? ''); const isScanning = ref(false); const crawlState = ref(null); const elapsedSeconds = ref(0); let timer: number | undefined; const handleScan = async () => { if (isScanning.value) { return; } isScanning.value = true; try { ensureElapsedTimer(); const response = await sendBackgroundMessage({ action: 'START_CRAWL', payload: { platformId: selectedPlatformId.value }, }); if (response.ok) { syncCrawlState(response.data ?? null); } else { console.error('[crawl] start failed', response.error); } } finally { isScanning.value = false; } }; const handleCancelCrawl = async () => { const response = await sendBackgroundMessage({ action: 'CANCEL_CRAWL' }); if (response.ok) { syncCrawlState(response.data ?? null); return; } console.error('[crawl] cancel failed', response.error); await refreshCrawlState(); }; const handleResumeCrawl = async () => { const response = await sendBackgroundMessage({ action: 'RESUME_CRAWL' }); if (response.ok) { syncCrawlState(response.data ?? null); return; } console.error('[crawl] resume failed', response.error); await refreshCrawlState(); }; const handleDismissCrawl = async () => { const response = await sendBackgroundMessage({ action: 'DISMISS_CRAWL' }); if (response.ok) { syncCrawlState(response.data ?? null); return; } console.error('[crawl] dismiss failed', response.error); await refreshCrawlState(); }; function syncCrawlState(state: CrawlTaskState | null) { crawlState.value = state; updateSeconds(); if (state && ACTIVE_STATUSES.has(state.status)) { ensureElapsedTimer(); return; } clearElapsedTimer(); } function ensureElapsedTimer() { if (timer !== undefined) { return; } timer = window.setInterval(() => { updateSeconds(); }, 1000); } function clearElapsedTimer() { if (timer === undefined) { return; } window.clearInterval(timer); timer = undefined; } function updateSeconds() { if (!crawlState.value) { elapsedSeconds.value = 0; return; } elapsedSeconds.value = Math.max(0, Math.floor((Date.now() - crawlState.value.startedAt) / 1000)); } async function refreshCrawlState() { const response = await sendBackgroundMessage({ action: 'GET_CRAWL_STATE' }); if (response.ok) { syncCrawlState(response.data ?? null); } } function handleStorageChanged(changes: Record, areaName: string) { if (areaName !== 'local') { return; } const change = changes[CRAWL_TASK_STORAGE_KEY]; if (!change) { return; } syncCrawlState(isCrawlTaskState(change.newValue) ? change.newValue : null); } onMounted(async () => { await refreshCrawlState(); if (typeof chrome !== 'undefined' && chrome.storage?.onChanged) { chrome.storage.onChanged.addListener(handleStorageChanged); } }); onUnmounted(() => { clearElapsedTimer(); if (typeof chrome !== 'undefined' && chrome.storage?.onChanged) { chrome.storage.onChanged.removeListener(handleStorageChanged); } }); return { selectedPlatformId, isScanning, crawlState, handleScan, handleCancelCrawl, handleResumeCrawl, handleDismissCrawl, elapsedSeconds, }; }; function isCrawlTaskState(value: unknown): value is CrawlTaskState { return typeof value === 'object' && value !== null && 'id' in value && 'steps' in value; }