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')
);
}