70 lines
3.1 KiB
TypeScript
70 lines
3.1 KiB
TypeScript
/**
|
|
* A deliberately narrow MAIN-world bridge for the mature 1688/Taobao/Tmall
|
|
* structured collectors. It never exposes cookies, headers, arbitrary window
|
|
* keys, or a generic fetch primitive; only public product-state projections.
|
|
*/
|
|
export default defineContentScript({
|
|
matches: [
|
|
'*://detail.1688.com/*',
|
|
'*://item.taobao.com/*',
|
|
'*://detail.tmall.com/*',
|
|
],
|
|
world: 'MAIN',
|
|
runAt: 'document_idle',
|
|
main() {
|
|
window.addEventListener('message', (event) => {
|
|
if (event.source !== window || !isRequest(event.data)) return;
|
|
const data = event.data.platform === '1688'
|
|
? project1688((window as unknown as Record<string, unknown>).context)
|
|
: projectTaobao(window as unknown as Record<string, unknown>);
|
|
window.postMessage({
|
|
source: 'atlas-collector',
|
|
type: 'source-page-state.response',
|
|
requestId: event.data.requestId,
|
|
data,
|
|
}, location.origin);
|
|
});
|
|
},
|
|
});
|
|
|
|
type Request = { source: 'atlas-collector'; type: 'source-page-state.request'; requestId: string; platform: '1688' | 'taobao' | 'tmall' };
|
|
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 === 'source-page-state.request'
|
|
&& typeof data.requestId === 'string' && /^[a-zA-Z0-9-]{16,120}$/.test(data.requestId)
|
|
&& (data.platform === '1688' || data.platform === 'taobao' || data.platform === 'tmall');
|
|
}
|
|
|
|
const safeClone = (value: unknown) => { try { return JSON.parse(JSON.stringify(value)); } catch { return undefined; } };
|
|
function find(value: unknown, key: string): unknown {
|
|
const pending: unknown[] = [value];
|
|
const seen = new Set<object>();
|
|
let visited = 0;
|
|
while (pending.length && visited < 300_000) {
|
|
const current = pending.shift();
|
|
if (!current || typeof current !== 'object' || seen.has(current)) continue;
|
|
seen.add(current);
|
|
visited += 1;
|
|
const record = current as Record<string, unknown>;
|
|
if (key in record) return record[key];
|
|
pending.push(...Object.values(record));
|
|
}
|
|
return undefined;
|
|
}
|
|
function project1688(context: unknown) {
|
|
const projected: Record<string, unknown> = {};
|
|
for (const key of ['gallery', 'tempModel', 'skuModel', 'tradeModel', 'skuMapOriginal', 'pieceWeightScaleInfo', 'productAttributes']) {
|
|
const value = find(context, key);
|
|
if (value !== undefined) projected[key] = safeClone(value);
|
|
}
|
|
return projected;
|
|
}
|
|
function projectTaobao(windowState: Record<string, unknown>) {
|
|
const ice = windowState.__ICE_APP_CONTEXT__ as Record<string, unknown> | undefined;
|
|
const result = ice?.loaderData && typeof ice.loaderData === 'object'
|
|
? (((ice.loaderData as Record<string, unknown>).home as Record<string, unknown> | undefined)?.data as Record<string, unknown> | undefined)?.res
|
|
: undefined;
|
|
return { product: safeClone(result) };
|
|
}
|