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 { // 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(() => { 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; // Tab is fully loaded if (changeInfo.status === 'complete' && updatedTab.url) { chrome.tabs.onUpdated.removeListener(checkTabLoaded); // Small delay to ensure DOM is ready setTimeout(() => { // Send parse message to content script chrome.tabs.sendMessage(tabId, { action: "PARSE_EBAY" }) .then(response => { if (response && response.ok && response.data) { handleParseComplete(tabId, response.data); } else { cleanupParseRequest(tabId, null, { ok: false, error: "Parsing failed" }); } }) .catch(err => { console.error("Error sending parse message:", err); cleanupParseRequest(tabId, null, { ok: false, error: "Content script error" }); }); }, 1000); // 1 second delay for DOM ready } }; chrome.tabs.onUpdated.addListener(checkTabLoaded); } catch (error) { console.error("Error in handleParseRequest:", error); sendResponse({ ok: false, error: error.message || "Unknown error" }); } } /** * 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(() => { // Send parse message to content script chrome.tabs.sendMessage(tabId, { action: "PARSE_PRODUCT_LIST" }) .then(response => { if (response && response.ok && response.data) { handleScanComplete(tabId, response.data); } else { cleanupScanRequest(tabId, null, { ok: false, error: response?.error || "Parsing failed" }); } }) .catch(err => { console.error("Error sending parse message:", err); cleanupScanRequest(tabId, null, { ok: false, error: "Content script error" }); }); }, 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" }); } } /** * 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) { request.sendResponse(error); } else if (data) { 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(); }