Skip to content

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 tabs
  • chrome.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 is chrome.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:

PermissionWhy it is needed
tabsRead 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.
nativeMessagingRequired 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

  1. Build the extension (npx wxt build).
  2. Open chrome://extensions, enable Developer Mode.
  3. Load unpacked and select grot-track-extension/.output/chrome-mv3/.
  4. Note the extension ID Chrome assigns.
  5. 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_origins entry. 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.