- Added detailed logging for various actions in the background script and content script to improve debugging capabilities. - Updated the account management flow to include the last updated timestamp and sales data. - Refined the parsing logic to ensure accurate extraction of seller statistics from eBay profiles. - Improved error handling in the parsing process to provide more informative responses in case of failures.
778 lines
34 KiB
JavaScript
778 lines
34 KiB
JavaScript
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<tabId, { timeout, originalSender, resolve }>
|
|
const activeScanRequests = new Map(); // Map<tabId, { timeout, sendResponse }>
|
|
|
|
// 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<boolean>} - 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();
|
|
}
|