import { collectCurrentProduct, supportedProductPlatform, } from '../lib/source-parsers.js'; import type { OzonPageJsonFetcher } from '../lib/ozon-parser.js'; export default defineContentScript({ matches: [ '*://www.ozon.ru/*', '*://www.ozon.kz/*', '*://www.ozon.by/*', '*://detail.1688.com/*', '*://item.taobao.com/*', '*://detail.tmall.com/*', ], runAt: 'document_idle', main() { let mount: HTMLDivElement | null = null; let shadow: ShadowRoot | null = null; let observedUrl = location.href; const render = async () => { if (!supportedProductPlatform()) { mount?.remove(); mount = null; shadow = null; return; } if (!mount) { mount = document.createElement('div'); mount.style.cssText = 'position:fixed;right:22px;bottom:80px;z-index:2147483647'; document.documentElement.append(mount); shadow = mount.attachShadow({ mode: 'open' }); } const root = shadow!; const session = await browser.runtime .sendMessage({ type: 'atlas.session' }) .then((value: { session?: unknown }) => value.session) .catch(() => null); root.innerHTML = `
Atlas ERP
${session ? '' : ''}
`; root.querySelector('.dot')?.addEventListener('click', () => { mount?.remove(); mount = null; shadow = null; }); root.querySelectorAll('[data-action]').forEach( (button) => button.addEventListener('click', () => void action(button.dataset.action ?? '', root), ), ); }; const action = async (kind: string, root: ShadowRoot) => { const hint = root.querySelector('[data-hint]')!; try { if (kind === 'login') { hint.textContent = '正在登录…'; await browser.runtime.sendMessage({ type: 'atlas.login' }); await render(); return; } if (kind === 'erp') { await browser.runtime.sendMessage({ type: 'atlas.open', path: '/' }); return; } hint.textContent = '正在采集多 SKU…'; const payload = await collectCurrentProduct( document, location.href, supportedProductPlatform() === 'ozon' ? requestOzonPageJson : undefined, ); const response = await browser.runtime.sendMessage({ type: 'atlas.collect', payload, }); if (kind === 'edit') { await browser.runtime.sendMessage({ type: 'atlas.open', path: response.result.data.editor_entry_path, }); } hint.textContent = kind === 'edit' ? '已打开采集数据测试页' : '已进入采集箱'; } catch (error) { hint.textContent = error instanceof Error ? error.message : '采集失败,请重试'; } }; void render(); setInterval(() => { if (location.href !== observedUrl) { observedUrl = location.href; void render(); } }, 700); }, }); const requestOzonPageJson: OzonPageJsonFetcher = ({ kind, sku }) => new Promise((resolve, reject) => { const requestId = crypto.randomUUID(); const timeout = window.setTimeout(() => { window.removeEventListener('message', receive); reject(new Error('Ozon 页面状态读取超时')); }, 15_000); const receive = (event: MessageEvent) => { if (event.source !== window || !isBridgeResponse(event.data, requestId)) return; window.clearTimeout(timeout); window.removeEventListener('message', receive); if (event.data.ok) resolve(event.data.data); else reject(new Error(event.data.error)); }; window.addEventListener('message', receive); window.postMessage( { source: 'atlas-collector', type: 'ozon-page-json.request', requestId, kind, sku, }, location.origin, ); }); function isBridgeResponse( value: unknown, requestId: string, ): value is { source: 'atlas-collector'; type: 'ozon-page-json.response'; requestId: string; ok: boolean; data?: unknown; error: string; } { if (!value || typeof value !== 'object' || Array.isArray(value)) return false; const message = value as Record; return ( message.source === 'atlas-collector' && message.type === 'ozon-page-json.response' && message.requestId === requestId && typeof message.ok === 'boolean' && (message.ok || typeof message.error === 'string') ); }