Architecture
July 21, 2025 ยท View on GitHub
This document describes the technical architecture of the Qlty Browser Extension, focusing on architecturally significant components that developers need to understand for maintenance and extension.
Overview
The Qlty Browser Extension provides code coverage insights directly within GitHub pull requests and commits. It integrates with the Qlty platform to fetch coverage data and inject visual indicators into GitHub's interface using a content script architecture.
Cross-Browser Compatibility
The extension supports both Chrome and Firefox using a unified codebase with webextension-polyfill for seamless cross-browser compatibility.
Browser API Compatibility
- Uses
webextension-polyfillfor cross-browser compatibility src/browser-api.ts- Simple re-export of the polyfill- All extension APIs use the standardized
browser.*interface with promises - Unified codebase works seamlessly in both Chrome and Firefox
Manifest System
The build system uses a single JavaScript-based manifest file with dynamic adaptation:
manifest.js- Single manifest file that supports environment variable interpolation- Chrome builds use Manifest v3 with service workers (default configuration)
- Firefox builds dynamically modify the manifest to use Manifest v2 with background pages
- The
@crxjs/vite-pluginprocesses the manifest based onTARGET_BROWSERenvironment variable - Environment variables like
VITE_API_URLandVITE_LOGIN_URLare interpolated during build - Firefox-specific processing removes Chrome-only manifest properties like
use_dynamic_url
Key Differences Handled
- Manifest versions: Chrome uses v3, Firefox uses v2 (dynamically converted)
- Background execution:
- Chrome uses service workers (
background.service_worker) - Firefox uses background page (
background.pagepointing toservice_worker.html)
- Chrome uses service workers (
- Manifest properties: Firefox build removes Chrome-specific properties
- API compatibility: Polyfill handles Chrome's callback-based vs Firefox's promise-based APIs automatically
Core Components
Service Worker (src/service_worker.ts)
The service worker acts as the central orchestrator for all extension functionality:
Key Responsibilities:
- API Communication: Manages all HTTP requests to the Qlty API at
https://api.qlty.sh - Authentication State: Stores and manages OAuth tokens using
browser.storage.sync - Tab Management: Handles opening/closing authentication tabs during OAuth flow
- Message Routing: Processes commands from content scripts via
browser.runtime.onMessage
Architecture Patterns:
- Command-based message system with typed TypeScript interfaces
- Token resolution logic (custom API token takes precedence over OAuth token)
- State-based authentication flow with cryptographic validation
- Automatic token cleanup on 4xx HTTP responses
Chrome vs Firefox:
- Chrome: Runs as a service worker (Manifest v3)
- Firefox: Runs as a background script (Manifest v2)
GitHub Page Injection (src/github-injector.ts)
The GitHub injection system is the core feature that provides coverage visualization:
Injection Strategy:
- DOM Monitoring: Uses
MutationObserverto detect GitHub page changes and re-inject UI - File Detection: Scans for file diff links (
a[href^="#diff-"]) to identify files needing coverage - Path Extraction: Extracts file paths from GitHub's diff anchor links
- Coverage Mapping: Maps line numbers to coverage data using GitHub's grid cell IDs
UI Components Injected:
- Coverage Badges: Visual indicators in PR/commit headers showing overall coverage status
- Line Gutters: Color-coded coverage indicators (green=covered, red=uncovered, gray=omitted)
- Coverage Summary: Percentage and progress bar for each file
- Navigation Button: "Jump to next uncovered line" functionality with keyboard shortcut (n key)
Technical Implementation:
- Caches coverage data per file path to avoid repeated API calls
- Handles both PR pages and standalone commit pages with different selectors
- Injects CSS classes (
qlty-coverage-hit,qlty-coverage-miss,qlty-coverage-omit) - Line number extraction from GitHub's
data-grid-cell-idanddata-line-numberattributes
Content Script Entry Point (src/coverage.ts):
- Simple entry point that imports CSS and calls
tryInjectDiffUI() - Loads on GitHub URLs matching pull requests and commits
Authentication Flow
The authentication system implements a secure OAuth-like flow:
OAuth State Management (src/service_worker.ts):
- State Generation: Creates cryptographically secure state with
crypto.randomUUID() - State Hashing: Generates SHA-256 hash of state for validation
- Tab Orchestration: Opens Qlty login page in new tab, remembers previous tab
- State Validation: Compares returned state hash with stored value
Token Exchange (src/auth.ts):
- URL Validation: Runs on
https://qlty.sh/auth/browser?*pages - State Verification: Validates state parameter against stored hash
- Token Generation: Makes authenticated request to
/api/user/access_tokens - Storage: Stores token in
browser.storage.syncfor persistence - Flow Completion: Notifies service worker to close auth tab
Security Features:
- State parameter prevents CSRF attacks
- Hash comparison ensures state integrity
- Automatic token cleanup on authentication failures
- Secure storage using browser sync storage
Settings Interface (src/settings.tsx)
React-based popup interface for extension configuration:
Component Architecture:
- Settings Container: Main component managing user state and storage listeners
- UserInfo Component: Displays login status, avatar, and sign-out functionality
- AdvancedSettings: Collapsible section for custom API token input
- NotSignedIn: Sign-in button for unauthenticated users
State Management:
- Uses React hooks with
browser.storage.syncintegration - Real-time updates via
browser.storage.onChangedlisteners - Cached user data includes login, avatar URL, and tokens
User Experience:
- Automatic token validation and user info display
- Custom API token override for enterprise/development use
- Direct link to Qlty settings page for token generation
API Layer (src/api.ts)
Centralized API communication layer:
Coverage Data Flow:
- Content script requests coverage via message passing
- Service worker constructs API URL with workspace/project/reference parameters
- Makes authenticated request to
/gh/{workspace}/projects/{project}/coverage/file - Returns structured coverage data with line-by-line hit counts
- Content script caches and applies coverage visualization
Message Passing Architecture
The extension uses a command-based message system for communication between content scripts and the service worker:
Message Types (src/types.d.ts):
getFileCoverage: Request coverage data for a specific filegetAuthStateHash: Retrieve authentication state hash for validationsignIn: Initiate OAuth authentication flowsignOut: Clear stored authentication tokensgetUser: Fetch current user informationendAuthFlow: Complete authentication and close auth tab
Communication Pattern:
- Content scripts send typed message commands via
browser.runtime.sendMessage - Service worker receives messages via
browser.runtime.onMessagelistener - Service worker processes command and sends response via callback
- All external API calls are centralized in the service worker
Type Safety:
- TypeScript interfaces ensure message structure consistency
- Overloaded
chrome.runtime.sendMessagedeclarations provide compile-time validation - Request/response pairs are strongly typed
Data Flow Architecture
Coverage Data Pipeline:
- Trigger: GitHub page load/navigation detected by
MutationObserver - File Discovery: Scan DOM for diff links to identify files
- Data Request: Content script sends
getFileCoveragemessage to service worker - API Call: Service worker fetches from
/gh/{workspace}/projects/{project}/coverage/file - Response Processing: Coverage data returned with line-by-line hit counts
- UI Injection: Content script injects visual indicators into GitHub DOM
- Caching: Coverage data cached per file path for performance
Authentication Data Flow:
- Initiation: User clicks "Sign In" in settings popup
- State Generation: Service worker creates secure random state
- Tab Opening: New tab opened to Qlty OAuth page with state parameter
- Token Exchange: Auth page validates state and generates API token
- Storage: Token stored in
browser.storage.syncfor persistence - Cleanup: Auth tab closed, previous tab restored
Environment Configuration
Build-time Configuration:
- Development mode uses
.env.developmentwith dotenvx - Environment variables for
VITE_API_URLandVITE_LOGIN_URLare interpolated into manifests - Default values: API at
https://api.qlty.sh, login athttps://qlty.sh
Runtime Configuration:
- API token storage in browser sync storage (persists across browser sessions)
- Custom API token override for enterprise/development environments
- User preferences cached locally with real-time sync
Key Integration Points
GitHub Integration:
- Content scripts match
https://github.com/*/pull/*andhttps://github.com/*/commit/* - DOM injection targets GitHub's specific CSS selectors and data attributes
- Handles both old and new GitHub UI versions
Authentication Integration:
- OAuth flow happens on
https://qlty.sh/auth/browser?*pages - Secure state validation prevents CSRF attacks
- Automatic token refresh and cleanup
API Integration:
- All API calls proxied through service worker to
https://api.qlty.sh - Bearer token authentication with automatic token resolution
- Structured error handling and user feedback
Browser Storage:
- Uses
browser.storage.syncfor cross-device synchronization - Stores API tokens, user profile data, and preferences
- Real-time updates via storage change listeners
Extension Points for Developers
Adding New Coverage Visualizations:
- Extend
injectIntoGutterCell()insrc/github-injector.ts:259 - Add new CSS classes in
src/coverage.css - Update
FileCoverageinterface insrc/types.d.tsif needed
Supporting Additional GitHub Pages:
- Update manifest content script matches
- Modify
tryInjectDiffUI()for new page types - Add new selectors to
src/github/components/selectors.ts
Adding New API Endpoints:
- Extend message types in
src/types.d.ts - Add command handler in
src/service_worker.ts:42-70 - Update API layer in
src/api.ts
Customizing Authentication:
- Modify OAuth flow in
loadAuthenticationPage()andauthorize() - Update security validation in
validateState() - Extend settings UI for additional auth options