Merge remote changes and update project files

This commit is contained in:
2026-01-26 06:48:58 +01:00
24 changed files with 2943 additions and 850 deletions

View File

@@ -2,9 +2,12 @@ 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 SCAN_TIMEOUT_MS = 45000; // 45 seconds (listing with _ipg=240 can be slow)
const activeParseRequests = new Map(); // Map<tabId, { timeout, originalSender, resolve }>
const activeScanRequests = new Map(); // Map<tabId, { timeout, sendResponse }>
const activeScanRequests = new Map(); // Map<tabId, { timeout, sendResponse }>;
/** Aktueller Scan-Fortschritt für GET_SCAN_PROGRESS (Polling durch Web-App) */
let currentScanProgress = null;
// Messages from content script (der von der Web-App kommt)
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
@@ -64,6 +67,11 @@ chrome.runtime.onMessageExternal.addListener((msg, sender, sendResponse) => {
handleScanProductsRequest(msg.url, msg.accountId, sendResponse);
return true; // async
}
if (msg?.action === "GET_SCAN_PROGRESS") {
sendResponse(currentScanProgress ?? { percent: 0, phase: "idle", total: 0, current: 0, complete: false });
return false;
}
});
/**
@@ -72,10 +80,6 @@ chrome.runtime.onMessageExternal.addListener((msg, sender, sendResponse) => {
*/
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" });
@@ -92,10 +96,6 @@ async function handleParseRequest(url, sendResponse) {
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(() => {
@@ -112,19 +112,11 @@ async function handleParseRequest(url, sendResponse) {
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(() => {
@@ -159,17 +151,9 @@ function isEbayUrl(url) {
*/
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;
@@ -177,157 +161,27 @@ async function ensureContentScriptInjected(tabId) {
// 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
// Inject full ebay-content-script.js so PING, PARSE_EBAY and PARSE_PRODUCT_LIST are all handled.
// The previous inline injection only handled PING+PARSE_EBAY; SCAN sends PARSE_PRODUCT_LIST.
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");
}
}
files: ["ebay-content-script.js"]
});
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
console.log("[BACKGROUND] ebay-content-script.js injected successfully");
} 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
console.error("[BACKGROUND] Failed to inject ebay-content-script:", injectError);
throw injectError;
}
@@ -337,31 +191,11 @@ async function ensureContentScriptInjected(tabId) {
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
}
} catch (testError) {
}
// Wait longer for the script to fully initialize and register message listeners
// Retry PING to verify the script is ready
@@ -370,10 +204,6 @@ async function ensureContentScriptInjected(tabId) {
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 });
@@ -382,44 +212,25 @@ async function ensureContentScriptInjected(tabId) {
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;
}
}
@@ -436,18 +247,10 @@ async function sendParseMessageWithRetry(tabId, attempt) {
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");
}
@@ -455,10 +258,6 @@ async function sendParseMessageWithRetry(tabId, attempt) {
// 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 {
@@ -470,10 +269,6 @@ async function sendParseMessageWithRetry(tabId, attempt) {
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 {
@@ -498,10 +293,6 @@ async function sendParseMessageWithRetry(tabId, attempt) {
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...`);
@@ -563,9 +354,32 @@ async function cleanupParseRequest(tabId, data, error) {
}
}
/**
* Transform account URL (e.g. /str/topmons) to listing URL with 240 items per page.
* Example: https://www.ebay.de/str/topmons?... → https://www.ebay.de/sch/i.html?_ssn=topmons&store_name=topmons&_ipg=240
*/
function transformAccountUrlTo240ItemsUrl(accountUrl) {
try {
const url = new URL(accountUrl);
const pathname = url.pathname;
const strMatch = pathname.match(/\/str\/([^\/\?]+)/);
if (!strMatch || !strMatch[1]) {
throw new Error("Could not extract account name from URL (expected /str/{name})");
}
const accountName = strMatch[1];
const baseUrl = `${url.protocol}//${url.hostname}`;
const listingUrl = `${baseUrl}/sch/i.html?_ssn=${encodeURIComponent(accountName)}&store_name=${encodeURIComponent(accountName)}&_ipg=240`;
return listingUrl;
} catch (e) {
throw new Error(`Failed to transform account URL: ${e.message}`);
}
}
/**
* Handles eBay product scan request
* Creates a hidden tab, waits for load, sends parse message to content script
* Transforms account URL to listing URL with _ipg=240, creates hidden tab, parses, then loads each item in separate tab.
*/
async function handleScanProductsRequest(url, accountId, sendResponse) {
try {
@@ -575,9 +389,18 @@ async function handleScanProductsRequest(url, accountId, sendResponse) {
return;
}
let targetUrl = url;
try {
targetUrl = transformAccountUrlTo240ItemsUrl(url);
console.log("[BACKGROUND] Scan using listing URL (240 items):", targetUrl);
} catch (transformErr) {
console.warn("[BACKGROUND] URL transform failed, using original URL:", transformErr.message);
// Fallback: use original URL (e.g. already /sch/ or non-/str/)
}
// Create hidden tab
const tab = await chrome.tabs.create({
url: url,
url: targetUrl,
active: false
});
@@ -594,14 +417,14 @@ async function handleScanProductsRequest(url, accountId, sendResponse) {
sendResponse: sendResponse
});
currentScanProgress = { percent: 0, phase: "listing", total: 0, current: 0, complete: false };
// 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);
@@ -655,8 +478,12 @@ async function sendScanMessageWithRetry(tabId, attempt) {
meta: meta
});
} else {
// Erfolg: sende items + meta
handleScanComplete(tabId, { items, meta });
if (currentScanProgress) {
currentScanProgress.phase = "details";
currentScanProgress.total = items.length;
currentScanProgress.percent = 10;
}
await handleScanComplete(tabId, { items, meta });
}
} else {
// Fehler: sende error + meta
@@ -709,11 +536,104 @@ async function sendScanMessageWithRetry(tabId, attempt) {
}
}
const ITEM_TAB_LOAD_TIMEOUT_MS = 3000;
/**
* Opens each item URL in a separate background tab, waits for load, parses detail page
* (title, price, currency, category, condition), merges into item, then closes tab.
*/
async function loadAndParseEachItemTab(items) {
if (!Array.isArray(items) || items.length === 0) return;
const total = items.length;
for (let i = 0; i < total; i++) {
const item = items[i];
if (!item || !item.url) continue;
try {
const tab = await chrome.tabs.create({
url: item.url,
active: false
});
await new Promise((resolve) => {
const listener = (tabId, changeInfo) => {
if (tabId === tab.id && changeInfo.status === "complete") {
chrome.tabs.onUpdated.removeListener(listener);
resolve();
}
};
chrome.tabs.onUpdated.addListener(listener);
setTimeout(() => {
chrome.tabs.onUpdated.removeListener(listener);
resolve();
}, ITEM_TAB_LOAD_TIMEOUT_MS);
});
const injected = await ensureContentScriptInjected(tab.id);
if (!injected) {
console.warn(`[BACKGROUND] Content script not ready in item tab ${i + 1}, skipping detail parse`);
}
try {
const response = injected
? await chrome.tabs.sendMessage(tab.id, { action: "PARSE_ITEM_DETAIL" })
: null;
if (response?.ok && response?.data) {
const d = response.data;
if (d.title != null && d.title !== "") item.title = d.title;
if (d.price != null) item.price = d.price;
if (d.currency != null && d.currency !== "") item.currency = d.currency;
if (d.category != null && d.category !== "") item.category = d.category;
if (d.condition != null && d.condition !== "") item.condition = d.condition;
if (d.quantityAvailable != null) item.quantityAvailable = d.quantityAvailable;
if (d.quantitySold != null) item.quantitySold = d.quantitySold;
if (d.watchCount != null) item.watchCount = d.watchCount;
if (d.inCartsCount != null) item.inCartsCount = d.inCartsCount;
}
} catch (parseErr) {
console.warn(`[BACKGROUND] PARSE_ITEM_DETAIL failed for ${item.url}:`, parseErr);
}
if (currentScanProgress && total > 0) {
currentScanProgress.current = i + 1;
currentScanProgress.percent = Math.min(99, 10 + Math.round((90 * (i + 1)) / total));
}
try {
await chrome.tabs.remove(tab.id);
} catch (removeErr) {
console.warn(`[BACKGROUND] Could not close item tab:`, removeErr);
}
} catch (e) {
console.warn(`[BACKGROUND] Failed to load/parse item tab ${i + 1}/${total}:`, item.url, e);
}
}
}
/**
* Handles scan complete response from content script
*/
function handleScanComplete(tabId, data) {
cleanupScanRequest(tabId, data, null);
async function handleScanComplete(tabId, data) {
const request = activeScanRequests.get(tabId);
if (request && request.timeout) {
clearTimeout(request.timeout);
request.timeout = null;
}
const items = data?.items || [];
if (items.length > 0) {
await loadAndParseEachItemTab(items);
}
if (currentScanProgress) {
currentScanProgress.percent = 100;
currentScanProgress.complete = true;
currentScanProgress.current = items.length;
}
await cleanupScanRequest(tabId, data, null);
}
/**
@@ -731,6 +651,8 @@ async function cleanupScanRequest(tabId, data, error) {
// Remove from active requests
activeScanRequests.delete(tabId);
currentScanProgress = null;
// Close tab (always, even on error)
try {
await chrome.tabs.remove(tabId);