Extension¶
GrotTrack Tab Tracker is a Manifest V3 Chrome extension, built with WXT (a TypeScript/Vite extension framework), that reports the active browser tab to the locally running GrotTrack macOS app. It has no UI beyond a status popup and makes no network requests of its own.
Source: grot-track-extension/. Config: grot-track-extension/wxt.config.ts.
What it observes¶
The background service worker (entrypoints/background.ts) listens for three Chrome events, each triggering a debounced (300ms) chrome.tabs.query({ active: true, currentWindow: true }) and a push of the result to the native host:
chrome.tabs.onActivated— the user switched tabschrome.tabs.onUpdated— the active tab's title or URL changed (e.g. navigation, page load)chrome.windows.onFocusChanged— the user switched to a different Chrome window (ignored when the new focused window ID ischrome.windows.WINDOW_ID_NONE, i.e. focus left Chrome entirely)
For each qualifying event it sends the active tab's title, url, tabId, windowId and a Date.now() timestamp to GrotTrackNativeHost — see Native Messaging for the exact message shape and wire protocol.
The extension connects to the native host on startup (chrome.runtime.connectNative) and reconnects automatically 5 seconds after any disconnect. It also answers a getStatus runtime message with { connected: boolean }, which the popup uses to show connection state.
Permissions¶
wxt.config.ts declares exactly two permissions:
| Permission | Why it is needed |
|---|---|
tabs | Read the active tab's title and URL so the local app can determine what the user is working on. Without it, chrome.tabs.query cannot see tab title/URL. |
nativeMessaging | Required to open a port to GrotTrackNativeHost via chrome.runtime.connectNative. Without it, the extension has no way to reach the local app at all. |
No host_permissions, no <all_urls>, no activeTab-driven content script, no storage. The extension does not read page content — only the tab metadata Chrome's tabs API exposes directly.
Data handling¶
Per PRIVACY.md: all data the extension collects (tab title, URL, tab/window IDs, timestamp) goes to the native host on the same machine over native messaging, and nowhere else. The extension makes no outbound network requests, and stores nothing itself — BrowserTabService inside GrotTrack.app is what caches the most recent tab. See Privacy for the full data-handling statement and chromestore.md for the exact permission-justification text submitted to the Chrome Web Store review process.
Packaging¶
cd grot-track-extension
npm ci
npx wxt build # unpacked output: .output/chrome-mv3/
npx wxt zip # upload-ready zip in .output/
The extension's own version lives in two places kept in sync by release-please: grot-track-extension/package.json (version) and grot-track-extension/wxt.config.ts (manifest.version, currently 0.15.1 — matched to the app's MARKETING_VERSION in project.yml). Chrome Web Store versions must strictly increase, so both files are listed as extra-files in release-please-config.json and bumped together on every release.
Icons are generated separately, not checked in as source: npm run generate-icons (at the repo root) runs scripts/generate-icons.mjs to produce icon-16.png, icon-48.png and icon-128.png in grot-track-extension/public/ from assets/icon.svg, which wxt.config.ts then references for both the extension icon set and the toolbar action icon.
Installing for development¶
- Build the extension (
npx wxt build). - Open
chrome://extensions, enable Developer Mode. - Load unpacked and select
grot-track-extension/.output/chrome-mv3/. - Note the extension ID Chrome assigns.
- In GrotTrack's Settings → Browser tab, install the native messaging host and paste in that extension ID — this writes the host manifest with the matching
allowed_originsentry. See Native Messaging for what happens before an ID is supplied.
See Releasing for how the extension is published to the Chrome Web Store in CI.