Built Profit Scout — a MV3 Chrome Extension that injects a React-powered sourcing panel directly onto Amazon product pages. Reads ASIN from the DOM, calls a Node.js backend for margin calc + supplier data, renders results in a shadow DOM panel. The MV3 service worker constraint was the biggest architectural challenge.
Content Script — Reading the Amazon DOM
// content.js — injected on amazon.com/dp/* pages
const getProductData = () => {
const asin = document.querySelector('[data-asin]')?.dataset.asin;
const price = document.querySelector('.a-price .a-offscreen')
?.textContent.replace('$','');
const title = document.querySelector('#productTitle')
?.textContent.trim();
return { asin, price: parseFloat(price), title };
};
chrome.runtime.sendMessage({ type: 'ANALYSE_PRODUCT', data: getProductData() });
Service Worker — MV3 Stateless Pattern
// service-worker.js
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.type === 'ANALYSE_PRODUCT') {
fetchAnalysis(msg.data).then(result => {
// Persist to storage — service workers can terminate any time
chrome.storage.local.set({ [msg.data.asin]: result });
sendResponse(result);
});
return true; // keep channel open for async
}
});
const fetchAnalysis = async ({ asin, price }) => {
const [fees, suppliers, demand] = await Promise.all([
fetch(`https://api.profitscout.io/fba-fees?asin=${asin}`).then(r=>r.json()),
fetch(`https://api.profitscout.io/suppliers?asin=${asin}`).then(r=>r.json()),
fetch(`https://api.profitscout.io/demand?asin=${asin}`).then(r=>r.json()),
]);
return { fees, suppliers, demand };
};
Shadow DOM Panel — Injecting React Without Conflicts
// inject-panel.js
const host = document.createElement('div');
host.id = 'profit-scout-root';
document.body.appendChild(host);
const shadow = host.attachShadow({ mode: 'open' });
const mountPoint = document.createElement('div');
shadow.appendChild(mountPoint);
// Inject styles into shadow root so Amazon CSS doesn't bleed in
const style = document.createElement('style');
style.textContent = PANEL_STYLES; // bundled CSS string
shadow.appendChild(style);
ReactDOM.createRoot(mountPoint).render();
MV3 Gotchas I Hit
- Service worker terminates after ~30s idle — never assume state persists. Use chrome.storage.local for everything.
- No XMLHttpRequest in service workers — use fetch() only.
- host_permissions required for cross-origin calls — declare your API domain in manifest.json.
- Content Security Policy — Amazon's CSP blocks inline scripts. Shadow DOM + bundled JS sidesteps this cleanly.
Built at Ai Soft Tech Solution — aisofttechsolution.com
Top comments (0)