DEV Community

Imtiaz Ahmad
Imtiaz Ahmad

Posted on

Building a Chrome Extension with Manifest V3, React, and a Shadow DOM Panel injected on Amazon published: true

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.

Uploading imageContent 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() });
Enter fullscreen mode Exit fullscreen mode

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 };
};
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

MV3 Gotchas I Hit

  1. Service worker terminates after ~30s idle — never assume state persists. Use chrome.storage.local for everything.
  2. No XMLHttpRequest in service workers — use fetch() only.
  3. host_permissions required for cross-origin calls — declare your API domain in manifest.json.
  4. 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)