import amazon from './amazon'; import homedepot from './homedepot'; import { uploadImage } from '~/logic/upload'; import { useLongTask } from '~/composables/useLongTask'; export interface AmazonPageWorkerSettings { searchItems?: Ref; detailItems?: Ref>; reviewItems?: Ref>; commitChangeIngerval?: number; } class AmazonPageWorkerFactory { public amazonWorker: ReturnType | null = null; public amazonWorkerSettings: AmazonPageWorkerSettings = {}; public buildAmazonPageWorker() { const { isRunning, startTask } = useLongTask(); const worker = amazon.getAmazonPageWorker(); const searchCache = [] as AmazonSearchItem[]; const detailCache = new Map(); const reviewCache = new Map(); const unsubscribeFuncs = [] as (() => void)[]; onMounted(() => { unsubscribeFuncs.push( ...[ worker.on('error', () => { worker.stop(); }), worker.on('item-links-collected', ({ objs }) => { updateSearchCache(objs); }), worker.on('item-base-info-collected', (ev) => { updateDetailCache(ev); }), worker.on('item-category-rank-collected', (ev) => { updateDetailCache(ev); }), worker.on('item-images-collected', (ev) => { updateDetailCache(ev); }), worker.on('item-top-reviews-collected', (ev) => { updateDetailCache(ev); }), worker.on('item-aplus-screenshot-collect', (ev) => { uploadImage(ev.base64data, `${ev.asin}.png`).then((url) => { url && updateDetailCache({ asin: ev.asin, aplus: url }); }); }), worker.on('item-review-collected', (ev) => { updateReviews(ev); }), ], ); }); onUnmounted(() => { unsubscribeFuncs.forEach((unsubscribe) => unsubscribe()); unsubscribeFuncs.splice(0, unsubscribeFuncs.length); }); const updateSearchCache = (data: AmazonSearchItem[]) => { searchCache.push(...data); }; const updateDetailCache = (data: { asin: string } & Partial) => { const asin = data.asin; if (detailCache.has(data.asin)) { const origin = detailCache.get(data.asin); detailCache.set(asin, { ...origin, ...data } as AmazonDetailItem); } else { detailCache.set(asin, data as AmazonDetailItem); } }; const updateReviews = (data: { asin: string; reviews: AmazonReview[] }) => { const { asin, reviews } = data; const values = reviewCache.get(asin) || []; const ids = new Set(values.map((item) => item.id)); for (const review of reviews) { ids.has(review.id) || values.push(review); } reviewCache.set(asin, values); }; const commitChange = () => { const { searchItems, detailItems, reviewItems } = this.amazonWorkerSettings; if (typeof searchItems !== 'undefined') { searchItems.value = searchItems.value.concat(searchCache); searchCache.splice(0, searchCache.length); } if (typeof detailItems !== 'undefined') { for (const [k, v] of detailCache.entries()) { detailItems.value.delete(k); // Trigger update detailItems.value.set(k, v); } detailCache.clear(); } if (typeof reviewItems !== 'undefined') { for (const [asin, reviews] of reviewCache.entries()) { if (reviewItems.value.has(asin)) { const adds = new Set(reviews.map((x) => x.id)); const newReviews = reviewItems.value .get(asin)! .filter((x) => !adds.has(x.id)) .concat(reviews); newReviews.sort((a, b) => dayjs(b.dateInfo).diff(dayjs(a.dateInfo))); reviewItems.value.delete(asin); // Trigger update reviewItems.value.set(asin, newReviews); } else { reviewItems.value.set(asin, reviews); } } reviewCache.clear(); } }; const taskWrapper = any>(func: T) => { const { commitChangeIngerval = 1500 } = this.amazonWorkerSettings; searchCache.splice(0, searchCache.length); detailCache.clear(); reviewCache.clear(); return (...params: Parameters) => startTask(async () => { const interval = setInterval(() => commitChange(), commitChangeIngerval); await func(...params); clearInterval(interval); commitChange(); }); }; const runDetailPageTask = taskWrapper(worker.runDetailPageTask.bind(worker)); const runSearchPageTask = taskWrapper(worker.runSearchPageTask.bind(worker)); const runReviewPageTask = taskWrapper(worker.runReviewPageTask.bind(worker)); return { isRunning, runSearchPageTask, runDetailPageTask, runReviewPageTask, on: worker.on.bind(worker), off: worker.off.bind(worker), once: worker.once.bind(worker), stop: worker.stop.bind(worker), }; } loadAmazonPageWorker(settings?: AmazonPageWorkerSettings) { if (settings) { this.amazonWorkerSettings = { ...this.amazonWorkerSettings, ...settings }; } if (!this.amazonWorker) { this.amazonWorker = this.buildAmazonPageWorker(); } return this.amazonWorker; } } export interface HomedepotWorkerSettings { detailItems?: Ref>; commitChangeIngerval?: number; } class HomedepotWorkerFactory { public homedepotWorkerSettings: HomedepotWorkerSettings = {}; public homedepotWorker: ReturnType | null = null; public buildHomedepotWorker() { const worker = homedepot.getHomedepotWorker(); const { isRunning, startTask } = useLongTask(); const detailCache = new Map(); const unsubscribeFuncs = [] as (() => void)[]; onMounted(() => { unsubscribeFuncs.push( ...[ worker.on('error', () => { worker.stop(); }), worker.on('detail-item-collected', (ev) => { const { item } = ev; if (detailCache.has(item.OSMID)) { const origin = detailCache.get(item.OSMID); detailCache.set(item.OSMID, { ...origin, ...item }); } else { detailCache.set(item.OSMID, item); } }), ], ); }); onUnmounted(() => { unsubscribeFuncs.forEach((unsubscribe) => unsubscribe()); unsubscribeFuncs.splice(0, unsubscribeFuncs.length); }); const commitChange = () => { const { detailItems } = this.homedepotWorkerSettings; if (typeof detailItems !== 'undefined') { for (const [k, v] of detailCache.entries()) { detailItems.value.delete(k); // Trigger update detailItems.value.set(k, v); } detailCache.clear(); } }; const taskWrapper = any>(func: T) => { const { commitChangeIngerval = 1500 } = this.homedepotWorkerSettings; return (...params: Parameters) => startTask(async () => { const interval = setInterval(() => commitChange(), commitChangeIngerval); await func(...params); clearInterval(interval); commitChange(); }); }; const runDetailPageTask = taskWrapper(worker.runDetailPageTask.bind(worker)); return { isRunning, runDetailPageTask, on: worker.on.bind(worker), off: worker.off.bind(worker), once: worker.once.bind(worker), stop: worker.stop.bind(worker), }; } loadHomedepotWorker(settings?: HomedepotWorkerSettings) { if (settings) { this.homedepotWorkerSettings = { ...this.homedepotWorkerSettings, ...settings }; } if (!this.homedepotWorker) { this.homedepotWorker = this.buildHomedepotWorker(); } return this.homedepotWorker; } } const amazonfacotry = new AmazonPageWorkerFactory(); const homedepotfactory = new HomedepotWorkerFactory(); export function usePageWorker( type: 'amazon', settings?: AmazonPageWorkerSettings, ): ReturnType; export function usePageWorker( type: 'homedepot', settings?: HomedepotWorkerSettings, ): ReturnType; export function usePageWorker(type: 'amazon' | 'homedepot', settings?: any) { switch (type) { case 'amazon': return amazonfacotry.loadAmazonPageWorker(settings); case 'homedepot': return homedepotfactory.loadHomedepotWorker(settings); default: throw new Error(`Unsupported page worker type: ${type}`); } }