/** * Popup Component * * This is the main UI that appears when the user clicks the extension icon. * It communicates with both the content script and background script. * * Communication Flow: * ┌─────────────────────────────────────────────────────────────────────┐ * │ POPUP │ * │ │ * │ On mount: │ * │ │ * │ 1. Popup ──GET_PAGE_INFO──► Content Script (via browser.tabs) │ * │ Popup ◄──PAGE_INFO_RESPONSE── Content Script │ * │ → Displays word count, link count, image count │ * │ │ * │ 2. Popup ──GET_VISIT_COUNT──► Background Script (via runtime) │ * │ Popup ◄──VISIT_COUNT_RESPONSE── Background Script │ * │ → Displays total pages tracked │ * │ │ * │ 3. Popup ──► browser.storage.local │ * │ → Reads username for greeting │ * └─────────────────────────────────────────────────────────────────────┘ * * Note: browser.tabs.sendMessage() sends to content script in specific tab * browser.runtime.sendMessage() sends to background script */ import {useEffect, useState} from 'react'; import type {FC} from 'react'; import browser, {Tabs} from 'webextension-polyfill'; import {getStorage} from '../utils/storage'; import { PageInfo, PageInfoResponseMessage, VisitCountResponseMessage, } from '../types/messages'; import {TabInfo} from './components/TabInfo/TabInfo'; import {FooterActions} from './components/FooterActions/FooterActions'; import styles from './Popup.module.scss'; function openWebPage(url: string): Promise { return browser.tabs.create({url}); } interface TabData { title: string; url: string; favIconUrl?: string; } const Popup: FC = () => { const [tabInfo, setTabInfo] = useState(null); const [pageInfo, setPageInfo] = useState(null); const [visitCount, setVisitCount] = useState(0); const [username, setUsername] = useState(''); useEffect(() => { // Get current tab info browser.tabs.query({active: true, currentWindow: true}).then((tabs) => { const tab = tabs[0]; if (tab) { setTabInfo({ title: tab.title || 'Unknown', url: tab.url || 'Unknown', favIconUrl: tab.favIconUrl, }); // Request page info from content script if (tab.id) { browser.tabs .sendMessage(tab.id, {type: 'GET_PAGE_INFO'}) .then((response: unknown) => { const res = response as PageInfoResponseMessage; if (res?.data) { setPageInfo(res.data); } }) .catch(() => { // Content script might not be injected on this page }); } } }); // Get visit count from background script browser.runtime .sendMessage({type: 'GET_VISIT_COUNT'}) .then((response: unknown) => { const res = response as VisitCountResponseMessage; if (res?.count !== undefined) { setVisitCount(res.count); } }) .catch(() => { // Background script might not be ready }); // Get username from storage getStorage(['username']).then(({username: storedUsername}) => { setUsername(storedUsername); }); }, []); const handleReloadTab = async (): Promise => { const tabs = await browser.tabs.query({ active: true, currentWindow: true, }); const tab = tabs[0]; if (tab?.id) { await browser.tabs.reload(tab.id); } }; return (

Web Extension Starter

{username &&

Hello, {username}!

}
{tabInfo && (
)} {/* Page Stats from Content Script */} {pageInfo && (

Page Stats

{pageInfo.wordCount} Words
{pageInfo.linkCount} Links
{pageInfo.imageCount} Images
)} {/* Visit Count from Background Script */}
Pages tracked: {visitCount}
=> openWebPage('/Options/options.html') } onGitHub={(): Promise => openWebPage( 'https://github.com/abhijithvijayan/web-extension-starter' ) } onSupport={(): Promise => openWebPage('https://www.buymeacoffee.com/abhijithvijayan') } />
); }; export default Popup;