106 lines
3.7 KiB
TypeScript
106 lines
3.7 KiB
TypeScript
/**
|
||
* Runs in Ozon's MAIN world so the request has exactly the same origin and
|
||
* session context as the page. It accepts only Atlas' two fixed, numeric-SKU
|
||
* routes and returns a small widget-state allowlist; it never exposes cookies,
|
||
* request headers or arbitrary fetch capability to the isolated script.
|
||
*/
|
||
export default defineContentScript({
|
||
matches: ['*://www.ozon.ru/*', '*://www.ozon.kz/*', '*://www.ozon.by/*'],
|
||
world: 'MAIN',
|
||
runAt: 'document_idle',
|
||
main() {
|
||
window.addEventListener('message', (event) => {
|
||
if (event.source !== window || !isRequest(event.data)) return;
|
||
void respond(event.data);
|
||
});
|
||
},
|
||
});
|
||
|
||
type Request = {
|
||
source: 'atlas-collector';
|
||
type: 'ozon-page-json.request';
|
||
requestId: string;
|
||
kind: 'modal-aspects' | 'product' | 'product-detail';
|
||
sku: string;
|
||
};
|
||
|
||
function isRequest(value: unknown): value is Request {
|
||
if (!value || typeof value !== 'object' || Array.isArray(value))
|
||
return false;
|
||
const data = value as Record<string, unknown>;
|
||
return (
|
||
data.source === 'atlas-collector' &&
|
||
data.type === 'ozon-page-json.request' &&
|
||
typeof data.requestId === 'string' &&
|
||
/^[a-zA-Z0-9-]{16,120}$/.test(data.requestId) &&
|
||
(data.kind === 'modal-aspects' ||
|
||
data.kind === 'product' ||
|
||
data.kind === 'product-detail') &&
|
||
typeof data.sku === 'string' &&
|
||
/^\d{4,}$/.test(data.sku)
|
||
);
|
||
}
|
||
|
||
async function respond(request: Request) {
|
||
try {
|
||
const pagePath =
|
||
request.kind === 'modal-aspects'
|
||
? `/modal/aspectsNew?product_id=${request.sku}`
|
||
: request.kind === 'product-detail'
|
||
? `/product/${request.sku}/?layout_container=pdpPage2column&layout_page_index=2`
|
||
: `/product/${request.sku}/`;
|
||
const response = await fetch(
|
||
`/api/entrypoint-api.bx/page/json/v2?url=${encodeURIComponent(pagePath)}`,
|
||
{ credentials: 'same-origin' },
|
||
);
|
||
if (!response.ok)
|
||
throw new Error(`Ozon 页面状态请求失败(HTTP ${response.status})`);
|
||
const value = await response.json();
|
||
window.postMessage(
|
||
{
|
||
source: 'atlas-collector',
|
||
type: 'ozon-page-json.response',
|
||
requestId: request.requestId,
|
||
ok: true,
|
||
data: allowlistedPageState(value),
|
||
},
|
||
location.origin,
|
||
);
|
||
} catch (error) {
|
||
window.postMessage(
|
||
{
|
||
source: 'atlas-collector',
|
||
type: 'ozon-page-json.response',
|
||
requestId: request.requestId,
|
||
ok: false,
|
||
error:
|
||
error instanceof Error
|
||
? error.message
|
||
: 'Ozon 页面状态请求失败',
|
||
},
|
||
location.origin,
|
||
);
|
||
}
|
||
}
|
||
|
||
function allowlistedPageState(value: unknown) {
|
||
if (!value || typeof value !== 'object' || Array.isArray(value))
|
||
return { widgetStates: {} };
|
||
const states = (value as { widgetStates?: unknown }).widgetStates;
|
||
if (!states || typeof states !== 'object' || Array.isArray(states))
|
||
return { widgetStates: {} };
|
||
const widgetStates: Record<string, unknown> = {};
|
||
for (const [name, state] of Object.entries(
|
||
states as Record<string, unknown>,
|
||
)) {
|
||
if (
|
||
/^web(Aspects|AspectsModal|ProductHeading|Price|Gallery|Description|RichContent|Features|ShortCharacteristics|DetailedCharacteristics|Characteristics)-/.test(
|
||
name,
|
||
)
|
||
) {
|
||
widgetStates[name] = state;
|
||
}
|
||
}
|
||
return { widgetStates };
|
||
}
|