const STORAGE_KEY = "auth_jwt"; const BACKEND_URL = "http://localhost:5173"; // TODO: Backend URL konfigurieren const PARSE_TIMEOUT_MS = 15000; // 15 seconds const SCAN_TIMEOUT_MS = 20000; // 20 seconds (seller listing pages can be slower) const activeParseRequests = new Map(); // Map const activeScanRequests = new Map(); // Map // Messages from content script (der von der Web-App kommt) chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { // Auth messages vom Content Script if (msg?.type === "AUTH_JWT" && msg.jwt) { chrome.storage.local.set({ [STORAGE_KEY]: msg.jwt }).then(() => { sendResponse({ ok: true }); }); return true; // async } if (msg?.type === "AUTH_CLEARED") { chrome.storage.local.remove(STORAGE_KEY).then(() => { sendResponse({ ok: true }); }); return true; } // API calls vom Popup if (msg?.type === "GET_JWT") { getJwt().then(jwt => { sendResponse({ jwt }); }); return true; } if (msg?.type === "CALL_API" && msg.path) { callProtectedApi(msg.path, msg.payload).then(data => { sendResponse({ ok: true, data }); }).catch(err => { sendResponse({ ok: false, error: err.message }); }); return true; } // eBay Parsing Request (from Web App via content script or directly) if (msg?.action === "PARSE_URL" && msg.url) { handleParseRequest(msg.url, sendResponse); return true; // async } // eBay Parsing Response (from eBay content script) if (msg?.action === "PARSE_COMPLETE") { handleParseComplete(sender.tab?.id, msg.data); return true; } }); // Handle messages from external web apps (externally_connectable) chrome.runtime.onMessageExternal.addListener((msg, sender, sendResponse) => { if (msg?.action === "PARSE_URL" && msg.url) { handleParseRequest(msg.url, sendResponse); return true; // async } if (msg?.action === "SCAN_PRODUCTS" && msg.url && msg.accountId) { handleScanProductsRequest(msg.url, msg.accountId, sendResponse); return true; // async } }); /** * Handles eBay URL parsing request * Creates a hidden tab, waits for load, sends parse message to content script */ async function handleParseRequest(url, sendResponse) { try { // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:73',message:'handleParseRequest: entry',data:{url},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{}); // #endregion // Validate URL if (!url || typeof url !== 'string' || !url.toLowerCase().includes('ebay.')) { sendResponse({ ok: false, error: "Invalid eBay URL" }); return; } console.log("[BACKGROUND] Creating tab for parse request:", url); // Create hidden tab const tab = await chrome.tabs.create({ url: url, active: false }); const tabId = tab.id; console.log("[BACKGROUND] Tab created:", tabId); // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:89',message:'handleParseRequest: tab created',data:{tabId,url:tab.url},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{}); // #endregion // Set up timeout const timeoutId = setTimeout(() => { cleanupParseRequest(tabId, null, { ok: false, error: "timeout" }); }, PARSE_TIMEOUT_MS); // Store request info activeParseRequests.set(tabId, { timeout: timeoutId, sendResponse: sendResponse }); // Wait for tab to load, then send parse message const checkTabLoaded = (updatedTabId, changeInfo, updatedTab) => { if (updatedTabId !== tabId) return; // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:104',message:'checkTabLoaded: tab update',data:{tabId:updatedTabId,status:changeInfo.status,url:updatedTab.url},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'C'})}).catch(()=>{}); // #endregion // Tab is fully loaded if (changeInfo.status === 'complete' && updatedTab.url) { chrome.tabs.onUpdated.removeListener(checkTabLoaded); console.log("[BACKGROUND] Tab loaded, sending parse message:", updatedTab.url); // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:108',message:'checkTabLoaded: tab complete',data:{tabId,url:updatedTab.url,isEbayUrl:isEbayUrl(updatedTab.url)},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'C'})}).catch(()=>{}); // #endregion // Wait longer for content script to auto-load (if it does) // Content scripts from manifest.json might need more time in hidden tabs setTimeout(() => { sendParseMessageWithRetry(tabId, 0); }, 2000); // 2 seconds delay - give content script time to auto-load } }; chrome.tabs.onUpdated.addListener(checkTabLoaded); } catch (error) { console.error("Error in handleParseRequest:", error); sendResponse({ ok: false, error: error.message || "Unknown error" }); } } /** * Checks if a URL is an eBay URL * @param {string} url - URL to check * @returns {boolean} - true if URL is an eBay URL */ function isEbayUrl(url) { if (!url) return false; const urlLower = url.toLowerCase(); return urlLower.includes('ebay.'); } /** * Injects the eBay content script manually if it's not already loaded * @param {number} tabId - Tab ID * @returns {Promise} - true if injection successful or already loaded */ async function ensureContentScriptInjected(tabId) { try { // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:143',message:'ensureContentScriptInjected: entry',data:{tabId},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{}); // #endregion // First, check if tab URL is an eBay URL const tab = await chrome.tabs.get(tabId); // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:147',message:'ensureContentScriptInjected: tab info',data:{tabId,url:tab.url,isEbayUrl:isEbayUrl(tab.url)},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'C'})}).catch(()=>{}); // #endregion if (!isEbayUrl(tab.url)) { console.log("[BACKGROUND] Tab is not an eBay URL:", tab.url); return false; } // Check if content script is already loaded by sending a ping try { // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:153',message:'ensureContentScriptInjected: sending PING',data:{tabId},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{}); // #endregion await chrome.tabs.sendMessage(tabId, { action: "PING" }); console.log("[BACKGROUND] Content script already loaded"); // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:156',message:'ensureContentScriptInjected: PING success',data:{tabId},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{}); // #endregion return true; } catch (pingError) { // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:158',message:'ensureContentScriptInjected: PING failed',data:{tabId,error:pingError.message,runtimeError:chrome.runtime.lastError?.message},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{}); // #endregion // Content script not loaded, try to inject it manually console.log("[BACKGROUND] Content script not found, injecting manually..."); try { // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:162',message:'ensureContentScriptInjected: attempting injection',data:{tabId},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'E'})}).catch(()=>{}); // #endregion // Get tab info to check frames const tabInfo = await chrome.tabs.get(tabId); // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:165',message:'ensureContentScriptInjected: tab info before injection',data:{tabId,url:tabInfo.url},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'F'})}).catch(()=>{}); // #endregion // Inject the message listener directly as a function (not as a file) // This ensures it runs in the correct context with access to chrome.runtime try { await chrome.scripting.executeScript({ target: { tabId: tabId, frameIds: [0] }, func: () => { // Register message listener directly if (typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.onMessage) { chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.action === "PING") { sendResponse({ ok: true, ready: true }); return true; } if (message.action === "PARSE_EBAY") { // Simple parsing - extract from URL and DOM try { const url = window.location.href; const hostname = window.location.hostname.toLowerCase(); // Extract market let market = "US"; if (hostname.includes('.de')) market = 'DE'; else if (hostname.includes('.uk')) market = 'UK'; else if (hostname.includes('.fr')) market = 'FR'; else if (hostname.includes('.it')) market = 'IT'; else if (hostname.includes('.es')) market = 'ES'; // Extract seller ID from URL let sellerId = ""; const usrMatch = url.match(/\/usr\/([^\/\?]+)/i); if (usrMatch && usrMatch[1]) { sellerId = usrMatch[1].trim(); } else { const strMatch = url.match(/\/str\/([^\/\?]+)/i); if (strMatch && strMatch[1]) { sellerId = strMatch[1].trim(); } } // Extract shop name let shopName = ""; try { const h1 = document.querySelector('h1'); if (h1) { shopName = h1.textContent?.trim() || ""; } } catch (e) {} // Extract items sold let itemsSold = null; try { const container = document.querySelector(".str-seller-card__store-stats-content"); if (container) { const divs = container.querySelectorAll("div"); for (const div of divs) { const divText = div.textContent || ""; if (divText.includes("Artikel verkauft")) { const span = div.querySelector('span[class*="BOLD"]') || div.querySelector("span.BOLD"); if (span) { let valueText = span.textContent?.trim() || ""; valueText = valueText.replace(/\s/g, "").replace(/[.,]/g, "").replace(/\D/g, ""); if (valueText.length > 0) { const parsedValue = parseInt(valueText, 10); if (!isNaN(parsedValue) && parsedValue >= 0) { itemsSold = parsedValue; } } } } } } } catch (e) {} sendResponse({ ok: true, data: { sellerId: sellerId || "", shopName: shopName || "", market: market, status: (sellerId || shopName) ? "active" : "unknown", stats: { itemsSold: itemsSold } } }); } catch (error) { sendResponse({ ok: true, data: { sellerId: "", shopName: "", market: "US", status: "unknown", stats: {} } }); } return true; } return false; }); console.log("[EBAY-CONTENT] Message listener registered"); } } }); console.log("[BACKGROUND] Content script message listener injected successfully"); // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:172',message:'ensureContentScriptInjected: listener injection success',data:{tabId,frameId:0},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H'})}).catch(()=>{}); // #endregion } catch (injectError) { console.error("[BACKGROUND] Failed to inject message listener:", injectError); // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:178',message:'ensureContentScriptInjected: listener injection failed',data:{tabId,error:injectError.message},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H'})}).catch(()=>{}); // #endregion throw injectError; } // Also try injecting a simple test script to verify injection works try { await chrome.scripting.executeScript({ target: { tabId: tabId, frameIds: [0] }, func: () => { console.log("[TEST-INJECTION] Test script executed in main frame"); // Try to send a log via fetch fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{ method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ location:'background.js:test-injection', message:'test injection: script executed', data:{url:window.location.href,hasChrome:typeof chrome!=='undefined',hasRuntime:typeof chrome!=='undefined'&&!!chrome.runtime}, timestamp:Date.now(), sessionId:'debug-session', runId:'run1', hypothesisId:'G' }) }).catch(()=>{}); } }); // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:207',message:'ensureContentScriptInjected: test injection success',data:{tabId},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'G'})}).catch(()=>{}); // #endregion } catch (testError) { // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:210',message:'ensureContentScriptInjected: test injection failed',data:{tabId,error:testError.message},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'G'})}).catch(()=>{}); // #endregion } // Wait longer for the script to fully initialize and register message listeners // Retry PING to verify the script is ready // Also try sending to frameId 0 explicitly for (let pingAttempt = 0; pingAttempt < 10; pingAttempt++) { await new Promise(resolve => setTimeout(resolve, 300)); // 300ms between pings try { // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:169',message:'ensureContentScriptInjected: ping after injection',data:{tabId,pingAttempt},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{}); // #endregion // Try sending to main frame explicitly try { await chrome.tabs.sendMessage(tabId, { action: "PING" }, { frameId: 0 }); } catch (frameErr) { // Fallback: try without frameId await chrome.tabs.sendMessage(tabId, { action: "PING" }); } // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:176',message:'ensureContentScriptInjected: ping success after injection',data:{tabId,pingAttempt},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{}); // #endregion console.log("[BACKGROUND] Content script ready after injection"); return true; } catch (pingErr) { // Continue to next attempt // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:182',message:'ensureContentScriptInjected: ping failed after injection',data:{tabId,pingAttempt,error:pingErr.message},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{}); // #endregion } } // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:189',message:'ensureContentScriptInjected: ping timeout after injection',data:{tabId},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{}); // #endregion // Even if PING failed, return true - the script might still work console.warn("[BACKGROUND] Content script injected but PING not responding"); return true; } catch (injectError) { console.error("[BACKGROUND] Failed to inject content script:", injectError); // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:172',message:'ensureContentScriptInjected: injection failed',data:{tabId,error:injectError.message,runtimeError:chrome.runtime.lastError?.message},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'E'})}).catch(()=>{}); // #endregion return false; } } } catch (error) { console.error("[BACKGROUND] Error checking/injecting content script:", error); // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:177',message:'ensureContentScriptInjected: error',data:{tabId,error:error.message},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{}); // #endregion return false; } } /** * Sends parse message to content script with retry mechanism * @param {number} tabId - Tab ID * @param {number} attempt - Current attempt number (0-based) */ async function sendParseMessageWithRetry(tabId, attempt) { const maxAttempts = 3; const retryDelay = 500; // 500ms between retries try { console.log(`[BACKGROUND] Sending parse message (attempt ${attempt + 1}/${maxAttempts}) to tab:`, tabId); // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:187',message:'sendParseMessageWithRetry: entry',data:{tabId,attempt},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'D'})}).catch(()=>{}); // #endregion // On first attempt, ensure content script is injected if (attempt === 0) { const injected = await ensureContentScriptInjected(tabId); // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:195',message:'sendParseMessageWithRetry: injection result',data:{tabId,injected},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{}); // #endregion if (!injected) { throw new Error("Could not inject content script"); } } // Check if content script is injected by trying to send a ping // If this fails, the content script might not be loaded yet // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:204',message:'sendParseMessageWithRetry: sending PARSE_EBAY',data:{tabId,attempt},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{}); // #endregion // Try sending to main frame explicitly first let response; try { response = await chrome.tabs.sendMessage(tabId, { action: "PARSE_EBAY" }, { frameId: 0 }); } catch (frameErr) { // Fallback: try without frameId response = await chrome.tabs.sendMessage(tabId, { action: "PARSE_EBAY" }); } console.log("[BACKGROUND] Parse response received:", response); // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:206',message:'sendParseMessageWithRetry: response received',data:{tabId,hasResponse:!!response,ok:response?.ok},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{}); // #endregion if (response && response.ok && response.data) { handleParseComplete(tabId, response.data); } else { cleanupParseRequest(tabId, null, { ok: false, error: response?.error || "Parsing failed" }); } } catch (err) { // Check if error is due to content script not being ready const runtimeError = chrome.runtime.lastError?.message || ""; const isContentScriptError = err.message?.includes("Could not establish connection") || err.message?.includes("Receiving end does not exist") || err.message?.includes("Could not inject content script") || runtimeError.includes("Receiving end does not exist") || runtimeError.includes("Could not establish connection"); console.log(`[BACKGROUND] Error sending parse message (attempt ${attempt + 1}):`, { error: err.message, runtimeError: runtimeError, isContentScriptError: isContentScriptError }); // #region agent log fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'background.js:217',message:'sendParseMessageWithRetry: error',data:{tabId,attempt,error:err.message,runtimeError,isContentScriptError},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{}); // #endregion if (isContentScriptError && attempt < maxAttempts) { // Content script not ready yet, retry after delay console.log(`[BACKGROUND] Content script not ready, retrying in ${retryDelay}ms...`); setTimeout(() => { sendParseMessageWithRetry(tabId, attempt + 1); }, retryDelay); } else { // Max retries reached or different error const errorMessage = isContentScriptError ? "Content script konnte nicht geladen werden. Bitte Extension neu laden." : (err.message || runtimeError || "Content script error"); cleanupParseRequest(tabId, null, { ok: false, error: errorMessage }); } } } /** * Handles parse complete response from content script */ function handleParseComplete(tabId, data) { cleanupParseRequest(tabId, data, null); } /** * Cleans up parse request: closes tab, clears timeout, sends response */ async function cleanupParseRequest(tabId, data, error) { const request = activeParseRequests.get(tabId); if (!request) return; // Clear timeout if (request.timeout) { clearTimeout(request.timeout); } // Remove from active requests activeParseRequests.delete(tabId); // Close tab try { await chrome.tabs.remove(tabId); } catch (err) { // Tab might already be closed console.warn("Could not close tab:", err); } // Send response if (request.sendResponse) { if (error) { request.sendResponse(error); } else if (data) { request.sendResponse({ ok: true, data: data }); } else { request.sendResponse({ ok: false, error: "Unknown error" }); } } } /** * Handles eBay product scan request * Creates a hidden tab, waits for load, sends parse message to content script */ async function handleScanProductsRequest(url, accountId, sendResponse) { try { // Validate URL if (!url || typeof url !== 'string' || !url.toLowerCase().includes('ebay.')) { sendResponse({ ok: false, error: "Invalid eBay URL" }); return; } // Create hidden tab const tab = await chrome.tabs.create({ url: url, active: false }); const tabId = tab.id; // Set up timeout const timeoutId = setTimeout(() => { cleanupScanRequest(tabId, null, { ok: false, error: "timeout" }); }, SCAN_TIMEOUT_MS); // Store request info activeScanRequests.set(tabId, { timeout: timeoutId, sendResponse: sendResponse }); // Wait for tab to load, then send parse message const checkTabLoaded = (updatedTabId, changeInfo, updatedTab) => { if (updatedTabId !== tabId) return; // Tab is fully loaded if (changeInfo.status === 'complete' && updatedTab.url) { chrome.tabs.onUpdated.removeListener(checkTabLoaded); // Small delay to ensure DOM is ready setTimeout(() => { sendScanMessageWithRetry(tabId, 0); }, 1000); // 1 second delay for DOM ready } }; chrome.tabs.onUpdated.addListener(checkTabLoaded); } catch (error) { console.error("Error in handleScanProductsRequest:", error); sendResponse({ ok: false, error: error.message || "Unknown error" }); } } /** * Sends scan message to content script with retry mechanism * @param {number} tabId - Tab ID * @param {number} attempt - Current attempt number (0-based) */ async function sendScanMessageWithRetry(tabId, attempt) { const maxAttempts = 3; const retryDelay = 500; // 500ms between retries try { console.log(`[BACKGROUND] Sending scan message (attempt ${attempt + 1}/${maxAttempts}) to tab:`, tabId); // On first attempt, ensure content script is injected if (attempt === 0) { const injected = await ensureContentScriptInjected(tabId); if (!injected) { throw new Error("Could not inject content script"); } } const response = await chrome.tabs.sendMessage(tabId, { action: "PARSE_PRODUCT_LIST" }); if (response && response.ok) { // Prüfe ob items vorhanden und nicht leer const items = response.items || response.data?.items || []; const meta = response.meta || response.data?.meta || {}; // Log meta für Debugging console.log("[SCAN meta]", meta); if (items.length === 0) { // Leere items: behandele als Fehler mit meta cleanupScanRequest(tabId, null, { ok: false, error: "empty_items", meta: meta }); } else { // Erfolg: sende items + meta handleScanComplete(tabId, { items, meta }); } } else { // Fehler: sende error + meta const meta = response?.meta || {}; console.log("[SCAN meta]", meta); cleanupScanRequest(tabId, null, { ok: false, error: response?.error || "Parsing failed", meta: meta }); } } catch (err) { // Check if error is due to content script not being ready const runtimeError = chrome.runtime.lastError?.message || ""; const isContentScriptError = err.message?.includes("Could not establish connection") || err.message?.includes("Receiving end does not exist") || err.message?.includes("Could not inject content script") || runtimeError.includes("Receiving end does not exist") || runtimeError.includes("Could not establish connection"); console.log(`[BACKGROUND] Error sending scan message (attempt ${attempt + 1}):`, { error: err.message, runtimeError: runtimeError, isContentScriptError: isContentScriptError }); if (isContentScriptError && attempt < maxAttempts) { // Content script not ready yet, retry after delay console.log(`[BACKGROUND] Content script not ready, retrying in ${retryDelay}ms...`); setTimeout(() => { sendScanMessageWithRetry(tabId, attempt + 1); }, retryDelay); } else { // Max retries reached or different error const errorMessage = isContentScriptError ? "Content script konnte nicht geladen werden. Bitte Extension neu laden." : (err.message || runtimeError || "Content script error"); cleanupScanRequest(tabId, null, { ok: false, error: errorMessage, meta: { pageType: "unknown", finalUrl: "", attempts: attempt + 1, reason: "content_script_error" } }); } } } /** * Handles scan complete response from content script */ function handleScanComplete(tabId, data) { cleanupScanRequest(tabId, data, null); } /** * Cleans up scan request: closes tab, clears timeout, sends response */ async function cleanupScanRequest(tabId, data, error) { const request = activeScanRequests.get(tabId); if (!request) return; // Clear timeout if (request.timeout) { clearTimeout(request.timeout); } // Remove from active requests activeScanRequests.delete(tabId); // Close tab (always, even on error) try { await chrome.tabs.remove(tabId); } catch (err) { // Tab might already be closed console.warn("Could not close tab:", err); } // Send response if (request.sendResponse) { if (error) { // error kann bereits meta enthalten request.sendResponse(error); } else if (data) { // data kann items + meta enthalten request.sendResponse({ ok: true, data: data }); } else { request.sendResponse({ ok: false, error: "Unknown error" }); } } } export async function getJwt() { const data = await chrome.storage.local.get(STORAGE_KEY); return data[STORAGE_KEY] || ""; } export async function callProtectedApi(path, payload) { const jwt = await getJwt(); if (!jwt) throw new Error("Not authed"); const res = await fetch(`${BACKEND_URL}${path}`, { method: "POST", headers: { "content-type": "application/json", "authorization": `Bearer ${jwt}` }, body: JSON.stringify(payload || {}) }); if (!res.ok) throw new Error(`API error: ${res.status}`); return await res.json(); }