const browserApi = globalThis.browser || null; const chromeApi = globalThis.chrome || null; const $ = selector => document.querySelector(selector); const state = { queue: [], serverUrl: 'http://localhost:8124' }; function sendMessage(message) { if (browserApi) return browserApi.runtime.sendMessage(message); return new Promise((resolve, reject) => { chromeApi.runtime.sendMessage(message, response => { const error = chromeApi.runtime.lastError; if (error) reject(new Error(error.message)); else resolve(response); }); }); } function storageGet(keys) { if (browserApi) return browserApi.storage.local.get(keys); return new Promise(resolve => chromeApi.storage.local.get(keys, resolve)); } function storageSet(values) { if (browserApi) return browserApi.storage.local.set(values); return new Promise(resolve => chromeApi.storage.local.set(values, resolve)); } function openTab(url) { if (browserApi) return browserApi.tabs.create({ url }); return chromeApi.tabs.create({ url }); } async function ensureServerPermission(url) { const parsed = new URL(url); const origin = `${parsed.protocol}//${parsed.host}/*`; const permissions = browserApi?.permissions || chromeApi?.permissions; if (!permissions) return true; if (browserApi) { if (await permissions.contains({ origins: [origin] })) return true; return permissions.request({ origins: [origin] }); } return new Promise(resolve => permissions.contains({ origins: [origin] }, has => has ? resolve(true) : permissions.request({ origins: [origin] }, resolve))); } async function api(path, method = 'GET', body) { const response = await sendMessage({ type: 'api', serverUrl: state.serverUrl, path, method, body }); if (!response?.ok) throw new Error(response?.data?.error || response?.error || 'Server nicht erreichbar'); return response.data; } function setConnection(ok, text) { const chip = $('#connection-chip'); chip.className = `connection-chip ${ok ? 'ok' : 'error'}`; chip.querySelector('span').textContent = text; } async function imageData(filename) { if (!filename) return ''; const url = `${state.serverUrl}/uploads/${String(filename).split('/').map(encodeURIComponent).join('/')}`; const response = await sendMessage({ type: 'fetch-image', url }); return response?.ok ? response.dataUrl : ''; } function formatPrice(value) { const num = Number(value); return Number.isFinite(num) ? `${num.toFixed(2).replace('.', ',')} €` : 'Preis fehlt'; } async function renderQueue() { const list = $('#queue-list'); const empty = $('#empty-card'); $('#list-count').textContent = `${state.queue.length} Einträge`; if (!state.queue.length) { list.innerHTML = ''; empty.classList.remove('hidden'); return; } empty.classList.add('hidden'); list.innerHTML = state.queue.map(item => `
${escapeHtml(item.title || 'Produktbild')}
${escapeHtml(item.title || 'Ohne Titel')}
${formatPrice(item.price)}
${escapeHtml([item.brand, item.size, item.condition].filter(Boolean).join(' · ') || 'Noch keine Eigenschaften')}
${escapeHtml(item.sku || `VD-${String(item.id).padStart(4, '0')}`)}
`).join(''); const imgNodes = [...list.querySelectorAll('img[data-photo]')]; for (const img of imgNodes) { const photo = img.dataset.photo; if (!photo) { img.removeAttribute('src'); continue; } const src = await imageData(photo); if (src) img.src = src; } list.querySelectorAll('[data-action]').forEach(button => button.addEventListener('click', async event => { const { action, id } = event.currentTarget.dataset; const listing = state.queue.find(item => item.id === Number(id)); if (!listing) return; if (action === 'fill') { await storageSet({ pendingFillListingId: listing.id, pendingFillIncludePhotos: false }); openTab('https://www.vinted.de/items/new'); window.close(); return; } if (action === 'fill-all') { await storageSet({ pendingFillListingId: listing.id, pendingFillIncludePhotos: true }); openTab('https://www.vinted.de/items/new'); window.close(); return; } if (action === 'detail') { await storageSet({ pendingOpenListingId: listing.id }); openTab('https://www.vinted.de/items/new'); window.close(); return; } if (action === 'app') { openTab(`${state.serverUrl}/#publish`); window.close(); } })); } function detectDiagnosticBrowser() { const ua = navigator.userAgent || ''; if (/Edg\//.test(ua)) return 'edge'; if (/Firefox\//.test(ua)) return 'firefox'; if (/Safari\//.test(ua) && !/Chrome\//.test(ua)) return 'safari'; return 'chrome'; } async function sendDiagnosticHeartbeat() { try { const stored = await storageGet(['vendooDiagnosticClientId']); const clientId = stored.vendooDiagnosticClientId || (globalThis.crypto?.randomUUID?.() || `vendoo-${Date.now()}-${Math.random().toString(16).slice(2)}`); if (!stored.vendooDiagnosticClientId) await storageSet({ vendooDiagnosticClientId: clientId }); await api('/api/extensions/heartbeat', 'POST', { client_id: clientId, browser: detectDiagnosticBrowser(), version: '1.9.0', platform: 'popup', server_url: state.serverUrl, capabilities: { popup: true, queue: true, publishing_queue: true, diagnostics: true }, }); } catch {} } async function loadQueue() { try { const data = await api('/api/extension/queue?platform=vinted'); state.queue = data.queue || []; setConnection(true, 'Verbunden'); await sendDiagnosticHeartbeat(); $('#queue-state').textContent = state.queue.length ? 'Bereit zum Ausfüllen' : 'Queue leer'; $('#queue-summary-text').textContent = state.queue.length ? 'Sichere Autofill-Aktionen ohne versteckte Veröffentlichung.' : 'Neue Vinted-Listings erscheinen automatisch hier.'; $('#queue-count').textContent = String(state.queue.length); await renderQueue(); } catch (error) { setConnection(false, 'Offline'); $('#queue-state').textContent = 'Server nicht erreichbar'; $('#queue-summary-text').textContent = error.message; state.queue = []; await renderQueue(); } } async function init() { const settings = await storageGet(['serverUrl', 'provider', 'language']); state.serverUrl = String(settings.serverUrl || 'http://localhost:8124').replace(/\/$/, ''); $('#server-url').value = state.serverUrl; if (settings.provider) $('#provider').value = settings.provider; if (settings.language) $('#language').value = settings.language; await loadQueue(); } $('#settings-toggle').addEventListener('click', () => { $('#dashboard-view').classList.add('hidden'); $('#settings-view').classList.remove('hidden'); }); $('#back-dashboard').addEventListener('click', () => { $('#settings-view').classList.add('hidden'); $('#dashboard-view').classList.remove('hidden'); }); $('#save-settings').addEventListener('click', async () => { state.serverUrl = $('#server-url').value.trim().replace(/\/$/, ''); if (!await ensureServerPermission(state.serverUrl)) throw new Error('Zugriff auf die Serveradresse wurde nicht erlaubt.'); await storageSet({ serverUrl: state.serverUrl, provider: $('#provider').value, language: $('#language').value }); $('#settings-view').classList.add('hidden'); $('#dashboard-view').classList.remove('hidden'); await loadQueue(); }); $('#reload-list').addEventListener('click', loadQueue); $('#open-queue').addEventListener('click', () => openTab(`${state.serverUrl}/#publish`)); $('#open-app').addEventListener('click', () => openTab(state.serverUrl)); $('#empty-open-app').addEventListener('click', () => openTab(state.serverUrl)); function escapeHtml(input) { return String(input ?? '').replace(/[&<>"']/g, ch => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[ch])); } setInterval(sendDiagnosticHeartbeat, 60 * 1000); init();