Merge remote changes and update project files
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -10,6 +10,10 @@ function setExtensionFlag() {
|
||||
const hasRuntime = hasChrome && chrome.runtime;
|
||||
const runtimeId = hasRuntime ? chrome.runtime.id : null;
|
||||
if (typeof window !== 'undefined' && hasChrome && hasRuntime && runtimeId) {
|
||||
// WICHTIG: Content Scripts haben einen isolierten Context
|
||||
// window.__EBAY_EXTENSION__ wird im Content Script Context gesetzt,
|
||||
// aber die Web-App läuft im Page Context - diese sind getrennt!
|
||||
// Daher verlassen wir uns hauptsächlich auf postMessage
|
||||
window.__EBAY_EXTENSION__ = true;
|
||||
window.__EBAY_EXTENSION_ID__ = runtimeId; // Extension-ID für chrome.runtime.sendMessage
|
||||
console.log('[ESHIP-CONTENT] window.__EBAY_EXTENSION__ set to true, ID:', runtimeId);
|
||||
@@ -23,6 +27,8 @@ function setExtensionFlag() {
|
||||
|
||||
// Versuche Flag sofort zu setzen
|
||||
console.log('[ESHIP-CONTENT] Content script loaded');
|
||||
console.log('[ESHIP-CONTENT] URL:', window.location.href);
|
||||
console.log('[ESHIP-CONTENT] ReadyState:', document.readyState);
|
||||
if (!setExtensionFlag()) {
|
||||
// Wenn window nicht verfügbar, warte auf DOMContentLoaded oder document.readyState
|
||||
if (document.readyState === 'loading') {
|
||||
@@ -35,34 +41,113 @@ if (!setExtensionFlag()) {
|
||||
setExtensionFlag();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
// Sende Extension-ID an Web-App via postMessage (da Content Script Isolation verhindert, dass window-Properties geteilt werden)
|
||||
// Die Web-App kann dann die Extension-ID in ihrem eigenen Context speichern
|
||||
function sendExtensionIdToWebApp() {
|
||||
try {
|
||||
const runtimeId = chrome.runtime?.id;
|
||||
const hasRuntime = !!chrome.runtime;
|
||||
const hasId = !!runtimeId;
|
||||
const currentUrl = window.location.href;
|
||||
|
||||
if (runtimeId) {
|
||||
// Sende Extension-ID an Web-App
|
||||
window.postMessage({
|
||||
const message = {
|
||||
source: "eship-extension",
|
||||
type: "EXTENSION_ID",
|
||||
extensionId: runtimeId
|
||||
}, "*");
|
||||
}
|
||||
};
|
||||
|
||||
// Sende Extension-ID an Web-App
|
||||
window.postMessage(message, "*");
|
||||
|
||||
} else {
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[ESHIP-CONTENT] Error sending extension ID:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Sende Extension-ID beim Laden
|
||||
// Sende Extension-ID sofort beim Laden (wichtig für Seiten-Reload)
|
||||
sendExtensionIdToWebApp();
|
||||
// Auch nach kurzer Verzögerung nochmal (falls Web-App noch nicht bereit ist)
|
||||
|
||||
// Sende auch nach kurzen Verzögerungen (falls Web-App noch nicht bereit ist)
|
||||
setTimeout(sendExtensionIdToWebApp, 100);
|
||||
setTimeout(sendExtensionIdToWebApp, 500);
|
||||
setTimeout(sendExtensionIdToWebApp, 1000);
|
||||
setTimeout(sendExtensionIdToWebApp, 2000);
|
||||
|
||||
// Kontinuierlich postMessage senden, damit die Web-App die Extension erkennen kann
|
||||
// (auch wenn der User die Extension NACH dem Laden der Seite installiert)
|
||||
let extensionAnnounceInterval = setInterval(() => {
|
||||
sendExtensionIdToWebApp();
|
||||
}, 2000); // Alle 2 Sekunden senden
|
||||
|
||||
// Stoppe das Intervall nach 5 Minuten (um Ressourcen zu sparen, aber lange genug für Onboarding)
|
||||
setTimeout(() => {
|
||||
if (extensionAnnounceInterval) {
|
||||
clearInterval(extensionAnnounceInterval);
|
||||
extensionAnnounceInterval = null;
|
||||
}
|
||||
}, 300000); // 5 Minuten
|
||||
|
||||
window.addEventListener("message", (event) => {
|
||||
// PING_EXTENSION Handler: Web-App fragt aktiv nach Extension
|
||||
// Akzeptiere sowohl "eship-webapp" als auch "eship-webapp-page-context"
|
||||
if ((event.data?.source === MESSAGE_SOURCE || event.data?.source === "eship-webapp-page-context") && event.data?.type === "PING_EXTENSION") {
|
||||
// Verwende console.log als Fallback, da fetch möglicherweise fehlschlägt
|
||||
console.log('[ESHIP-CONTENT] Received PING_EXTENSION, responding...', event.data);
|
||||
|
||||
// Antworte sofort mit Extension-ID
|
||||
const runtimeId = chrome.runtime?.id;
|
||||
console.log('[ESHIP-CONTENT] Runtime ID:', runtimeId);
|
||||
|
||||
if (runtimeId) {
|
||||
const response = {
|
||||
source: "eship-extension",
|
||||
type: "EXTENSION_ID",
|
||||
extensionId: runtimeId,
|
||||
requestId: event.data?.requestId
|
||||
};
|
||||
|
||||
console.log('[ESHIP-CONTENT] Sending response:', response);
|
||||
window.postMessage(response, "*");
|
||||
|
||||
// Setze user_extension_load auf true im Backend
|
||||
// Hole JWT aus Storage und sende an Backend
|
||||
chrome.storage.local.get("auth_jwt").then((data) => {
|
||||
const jwt = data.auth_jwt;
|
||||
if (jwt) {
|
||||
// Backend-Endpoint aufrufen
|
||||
fetch("http://localhost:3000/api/user/set-extension-loaded", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${jwt}`
|
||||
}
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(result => {
|
||||
console.log('[ESHIP-CONTENT] user_extension_load set to true:', result);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('[ESHIP-CONTENT] Failed to set user_extension_load:', err);
|
||||
});
|
||||
} else {
|
||||
console.warn('[ESHIP-CONTENT] No JWT found, cannot set user_extension_load');
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
console.error('[ESHIP-CONTENT] No runtime ID available!');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Sicherheitscheck: Nur Nachrichten von derselben Origin akzeptieren
|
||||
if (event.data?.source !== MESSAGE_SOURCE) return;
|
||||
if (event.data?.source !== MESSAGE_SOURCE && event.data?.source !== "eship-webapp-page-context") return;
|
||||
|
||||
// Auth Messages (JWT)
|
||||
if (event.data.type === "AUTH_JWT" || event.data.type === "AUTH_CLEARED") {
|
||||
|
||||
@@ -6,29 +6,15 @@
|
||||
// Log dass Content Script geladen wurde
|
||||
console.log("[EBAY-CONTENT] Content script loaded on:", window.location.href);
|
||||
|
||||
// #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:'ebay-content-script.js:7',message:'ebay-content-script: loaded',data:{url:window.location.href,readyState:document.readyState},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{});
|
||||
// #endregion
|
||||
|
||||
// Message Listener für Parsing-Anfragen
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
// #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:'ebay-content-script.js:10',message:'ebay-content-script: message received',data:{action:message.action},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{});
|
||||
// #endregion
|
||||
|
||||
// Ping-Handler für Content Script Verfügbarkeitsprüfung
|
||||
if (message.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:'ebay-content-script.js:13',message:'ebay-content-script: PING handler',data:{},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{});
|
||||
// #endregion
|
||||
sendResponse({ ok: true, ready: true });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.action === "PARSE_EBAY") {
|
||||
// #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:'ebay-content-script.js:17',message:'ebay-content-script: PARSE_EBAY handler',data:{readyState:document.readyState},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{});
|
||||
// #endregion
|
||||
// Wrapper um sicherzustellen, dass immer eine Antwort gesendet wird
|
||||
try {
|
||||
// Prüfe ob DOM bereit ist
|
||||
@@ -75,6 +61,17 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
return true; // async response
|
||||
}
|
||||
|
||||
if (message.action === "PARSE_ITEM_DETAIL") {
|
||||
try {
|
||||
const detail = parseItemDetailPage();
|
||||
sendResponse({ ok: true, data: detail });
|
||||
} catch (e) {
|
||||
console.warn("[EBAY-CONTENT] PARSE_ITEM_DETAIL error:", e);
|
||||
sendResponse({ ok: true, data: {} });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.action === "PARSE_PRODUCT_LIST") {
|
||||
// async function, need to return promise
|
||||
parseProductList()
|
||||
@@ -183,6 +180,12 @@ function extractSellerId() {
|
||||
return strMatch[1].trim();
|
||||
}
|
||||
|
||||
// Pattern: _ssn=username (Seller Name Parameter in Suchergebnis-URLs)
|
||||
const ssnMatch = url.match(/[?&]_ssn=([^&]+)/i);
|
||||
if (ssnMatch && ssnMatch[1]) {
|
||||
return decodeURIComponent(ssnMatch[1]).trim();
|
||||
}
|
||||
|
||||
// Methode 2: DOM-Elemente suchen
|
||||
// Suche nach verschiedenen Selektoren, die Seller-ID enthalten könnten
|
||||
const possibleSelectors = [
|
||||
@@ -683,7 +686,6 @@ async function parseProductList() {
|
||||
let pageType = detectPageType();
|
||||
let finalUrl = window.location.href;
|
||||
let reason = null;
|
||||
|
||||
try {
|
||||
// Schritt 1: Warte auf Item-Links (Polling)
|
||||
let links = await waitForItemLinks(4000, 250, 5);
|
||||
@@ -768,17 +770,14 @@ async function parseProductList() {
|
||||
}
|
||||
}
|
||||
|
||||
// Return max 60 items (first page)
|
||||
const result = parsedItems.slice(0, 60);
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
items: result,
|
||||
items: parsedItems,
|
||||
meta: {
|
||||
pageType,
|
||||
finalUrl,
|
||||
attempts,
|
||||
reason: reason || (result.length > 0 ? "items_found" : "parsed_zero_items")
|
||||
reason: reason || (parsedItems.length > 0 ? "items_found" : "parsed_zero_items")
|
||||
}
|
||||
};
|
||||
|
||||
@@ -796,66 +795,103 @@ async function parseProductList() {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parst Produktdetail-Seite (/itm/...) für title, price, currency, category, condition,
|
||||
* quantityAvailable, quantitySold, watchCount, inCartsCount.
|
||||
*/
|
||||
function parseItemDetailPage() {
|
||||
const out = {
|
||||
title: "",
|
||||
price: null,
|
||||
currency: null,
|
||||
category: null,
|
||||
condition: null,
|
||||
quantityAvailable: null,
|
||||
quantitySold: null,
|
||||
watchCount: null,
|
||||
inCartsCount: null
|
||||
};
|
||||
try {
|
||||
const url = window.location.href;
|
||||
const urlLower = url.toLowerCase();
|
||||
|
||||
// Determine page type
|
||||
const isStorePage = urlLower.includes('/str/') || urlLower.includes('/store/');
|
||||
const isSellerPage = urlLower.includes('/usr/');
|
||||
|
||||
// Check if seller profile without items (try to find link to listings)
|
||||
if (isSellerPage && !isStorePage) {
|
||||
const itemsLink = document.querySelector('a[href*="/usr/"][href*="?items="]') ||
|
||||
document.querySelector('a[href*="schid=mksr"]') ||
|
||||
Array.from(document.querySelectorAll('a')).find(a => {
|
||||
const text = (a.textContent || '').toLowerCase();
|
||||
return text.includes('artikel') || text.includes('angebote') ||
|
||||
text.includes('items for sale') || text.includes('see all items');
|
||||
});
|
||||
|
||||
if (!itemsLink) {
|
||||
// Try to find item cards directly
|
||||
const hasItems = findItemLinks().length > 0;
|
||||
if (!hasItems) {
|
||||
throw new Error("no_items_page");
|
||||
const titleEl = document.querySelector("[data-testid=\"x-item-title\"] h1 .ux-textspans") ||
|
||||
document.querySelector("h1.x-item-title__mainTitle .ux-textspans");
|
||||
if (titleEl) {
|
||||
out.title = (titleEl.textContent || "").trim().replace(/\s+/g, " ");
|
||||
}
|
||||
|
||||
const priceEl = document.querySelector("[data-testid=\"x-price-primary\"] .ux-textspans");
|
||||
if (priceEl) {
|
||||
const priceText = (priceEl.textContent || "").trim();
|
||||
const parsed = parsePrice(priceText);
|
||||
out.price = parsed.price;
|
||||
out.currency = parsed.currency;
|
||||
}
|
||||
|
||||
const breadcrumbs = document.querySelectorAll("a.seo-breadcrumb-text span");
|
||||
if (breadcrumbs.length > 0) {
|
||||
const parts = [];
|
||||
breadcrumbs.forEach((span) => {
|
||||
const t = (span.textContent || "").trim();
|
||||
if (t) parts.push(t);
|
||||
});
|
||||
out.category = parts.join(" > ");
|
||||
}
|
||||
|
||||
const condEl = document.querySelector("dd.ux-labels-values__values .ux-textspans");
|
||||
if (condEl) {
|
||||
const raw = (condEl.textContent || "").trim();
|
||||
const colonIdx = raw.indexOf(":");
|
||||
out.condition = colonIdx >= 0 ? raw.slice(0, colonIdx).trim() : raw.slice(0, 80);
|
||||
}
|
||||
|
||||
// x-quantity__availability: "9 verfügbar", "8 verkauft"
|
||||
const availEl = document.querySelector(".x-quantity__availability");
|
||||
if (availEl) {
|
||||
const text = (availEl.textContent || "").trim();
|
||||
const verfMatch = text.match(/(\d+)\s*verfügbar/i);
|
||||
if (verfMatch && verfMatch[1]) {
|
||||
const n = parseInt(verfMatch[1], 10);
|
||||
if (!isNaN(n) && n >= 0) out.quantityAvailable = n;
|
||||
}
|
||||
const verkMatch = text.match(/(\d+)\s*verkauft/i);
|
||||
if (verkMatch && verkMatch[1]) {
|
||||
const n = parseInt(verkMatch[1], 10);
|
||||
if (!isNaN(n) && n >= 0) out.quantitySold = n;
|
||||
}
|
||||
}
|
||||
|
||||
// x-watch-heart-btn: .x-watch-heart-btn-text "14" oder aria-label "14 Beobachter"
|
||||
const watchBtn = document.querySelector("button.x-watch-heart-btn");
|
||||
if (watchBtn) {
|
||||
const textEl = watchBtn.querySelector(".x-watch-heart-btn-text");
|
||||
if (textEl) {
|
||||
const n = parseInt((textEl.textContent || "").trim().replace(/\D/g, ""), 10);
|
||||
if (!isNaN(n) && n >= 0) out.watchCount = n;
|
||||
}
|
||||
if (out.watchCount == null) {
|
||||
const label = (watchBtn.getAttribute("aria-label") || "").trim();
|
||||
const m = label.match(/(\d+)\s*Beobachter/i);
|
||||
if (m && m[1]) {
|
||||
const n = parseInt(m[1], 10);
|
||||
if (!isNaN(n) && n >= 0) out.watchCount = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract items
|
||||
const items = findItemLinks();
|
||||
|
||||
if (items.length === 0) {
|
||||
throw new Error("no_items_found");
|
||||
}
|
||||
|
||||
// Parse each item
|
||||
const parsedItems = [];
|
||||
const seenIds = new Set();
|
||||
|
||||
for (const itemLink of items) {
|
||||
try {
|
||||
const item = parseItemFromLink(itemLink);
|
||||
|
||||
// Deduplicate by platformProductId
|
||||
if (item.platformProductId && !seenIds.has(item.platformProductId)) {
|
||||
seenIds.add(item.platformProductId);
|
||||
parsedItems.push(item);
|
||||
}
|
||||
} catch (e) {
|
||||
// Continue with next item if one fails
|
||||
console.warn("Failed to parse item:", e);
|
||||
|
||||
// [data-testid="x-ebay-signal"]: "In 8 Warenkörben"
|
||||
const signalEl = document.querySelector("[data-testid=\"x-ebay-signal\"]");
|
||||
if (signalEl) {
|
||||
const text = (signalEl.textContent || "").trim();
|
||||
const m = text.match(/in\s*(\d+)\s*warenkörben/i);
|
||||
if (m && m[1]) {
|
||||
const n = parseInt(m[1], 10);
|
||||
if (!isNaN(n) && n >= 0) out.inCartsCount = n;
|
||||
}
|
||||
}
|
||||
|
||||
// Return max 60 items (first page)
|
||||
return parsedItems.slice(0, 60);
|
||||
|
||||
} catch (error) {
|
||||
// Re-throw to be caught by message handler
|
||||
throw error;
|
||||
} catch (e) {
|
||||
console.warn("[EBAY-CONTENT] parseItemDetailPage error:", e);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user