Development
Build, test, and contribute to the PRIV Chrome extension.
Development
This guide covers setting up the development environment, building the extension, and contributing to the codebase.
Project Structure
extensions/chrome/
├── public/
│ ├── manifest.json # Extension manifest (Manifest V3)
│ └── icons/ # Extension icons
├── src/
│ ├── background/
│ │ └── service-worker.ts # Background service worker
│ ├── content/
│ │ └── content-script.ts # Content script (page injection)
│ ├── popup/
│ │ ├── index.tsx # Popup entry point
│ │ ├── App.tsx # Main app component
│ │ ├── components/ # UI components
│ │ │ ├── Dashboard.tsx
│ │ │ ├── Header.tsx
│ │ │ ├── Settings.tsx
│ │ │ ├── EarningsCard.tsx
│ │ │ ├── EarningsHistory.tsx
│ │ │ ├── DataTypesPanel.tsx
│ │ │ ├── DataToggle.tsx
│ │ │ └── WalletConnect.tsx
│ │ └── hooks/ # React hooks
│ │ ├── useSettings.ts
│ │ └── useEarnings.ts
│ ├── lib/
│ │ ├── api.ts # API client
│ │ ├── collector.ts # Data collection utilities
│ │ ├── config.ts # Configuration
│ │ ├── security.ts # Security utilities
│ │ ├── storage.ts # Chrome storage wrapper
│ │ ├── sync.ts # Sync service
│ │ ├── utils.ts # General utilities
│ │ └── wallet.ts # Wallet integration
│ └── types/
│ └── index.ts # TypeScript types
├── scripts/
│ └── create-icons.js # Icon generation script
├── package.json
├── tsconfig.json
├── tailwind.config.ts
└── vite.config.tsPrerequisites
| Requirement | Version |
|---|---|
| Node.js | 18+ |
| npm | 9+ |
| Chrome | 110+ |
Install Node.js
# Using nvm (recommended)
nvm install 18
nvm use 18
# Verify installation
node --version # Should be 18.x or higher
npm --version # Should be 9.x or higherBuild Commands
Install Dependencies
cd extensions/chrome
npm installDevelopment Build
Build with watch mode for development:
npm run devThis command:
- Builds the extension to
dist/ - Watches for file changes
- Rebuilds automatically on changes
- Includes source maps for debugging
Production Build
Build optimized for production:
npm run build:prodThis command:
- Runs TypeScript type checking
- Generates extension icons
- Builds minified output
- Excludes source maps
- Sets production API endpoints
All Available Scripts
| Command | Description |
|---|---|
npm run dev | Development build with watch |
npm run build | Standard build |
npm run build:prod | Production build |
npm run typecheck | TypeScript type checking |
npm run lint | ESLint code linting |
npm run test | Run test suite |
npm run test:watch | Run tests in watch mode |
npm run test:coverage | Run tests with coverage |
npm run icons | Generate extension icons |
npm run clean | Remove build output |
Environment Variables
Configuration File
Environment configuration is in src/lib/config.ts:
// API Configuration
export const config = {
apiUrl: process.env.NODE_ENV === 'production'
? 'https://api.privprotocol.xyz'
: 'https://api-staging.privprotocol.xyz',
privTokenAddress: '0x...',
stakingAddress: '0x...',
// Feature flags
enableDebugLogs: process.env.NODE_ENV !== 'production',
};Development vs Production
| Variable | Development | Production |
|---|---|---|
| API URL | Staging server | Production server |
| Debug logs | Enabled | Disabled |
| Source maps | Included | Excluded |
| Minification | Disabled | Enabled |
Loading the Extension
Load Unpacked Extension
-
Run the build:
npm run build -
Open Chrome and navigate to
chrome://extensions -
Enable Developer mode (top right toggle)
-
Click Load unpacked
-
Select the
extensions/chrome/distfolder
[Screenshot: Chrome extensions page with Developer mode enabled]
Reload After Changes
After rebuilding:
- Go to
chrome://extensions - Find PRIV Protocol
- Click the refresh icon
Or use the keyboard shortcut (if enabled in Chrome).
Debugging
Service Worker Debugging
Debug the background service worker:
- Go to
chrome://extensions - Find PRIV Protocol
- Click Inspect views: service worker
- DevTools opens for the service worker
[Screenshot: Service worker DevTools console]
Content Script Debugging
Debug content scripts on a page:
- Open DevTools on any webpage (F12)
- Go to Sources tab
- Find
content-scriptsin the file tree - Set breakpoints in content-script.js
Popup Debugging
Debug the popup UI:
- Right-click the extension icon
- Select Inspect popup
- DevTools opens for the popup
Debug Logging
Enable debug logs in development:
import { debugLog, debugWarn, debugError } from '@/lib/config';
// These only log in development mode
debugLog('Info message', data);
debugWarn('Warning message', data);
debugError('Error message', error);Testing
Test Framework
The extension uses Vitest for testing:
# Run all tests
npm test
# Watch mode
npm run test:watch
# With coverage
npm run test:coverageTest Structure
// src/lib/security.test.ts
import { describe, it, expect } from 'vitest';
import { sanitizeWalletAddress, escapeHtml } from './security';
describe('sanitizeWalletAddress', () => {
it('should validate correct addresses', () => {
const address = '0x1234567890123456789012345678901234567890';
expect(sanitizeWalletAddress(address)).toBe(address.toLowerCase());
});
it('should reject invalid addresses', () => {
expect(sanitizeWalletAddress('invalid')).toBeNull();
expect(sanitizeWalletAddress('0x123')).toBeNull();
});
});
describe('escapeHtml', () => {
it('should escape HTML characters', () => {
expect(escapeHtml('<script>')).toBe('<script>');
expect(escapeHtml('"test"')).toBe('"test"');
});
});Coverage Report
npm run test:coverageCoverage report is generated in coverage/ directory.
Code Style
TypeScript Configuration
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noEmit": true,
"jsx": "react-jsx",
"paths": {
"@/*": ["./src/*"]
}
}
}ESLint Configuration
# Run linting
npm run lint
# Auto-fix issues
npm run lint -- --fixImport Aliases
Use the @/ alias for imports:
// Good
import { config } from '@/lib/config';
import type { UserSettings } from '@/types';
// Avoid
import { config } from '../../../lib/config';Component Development
Creating a New Component
// src/popup/components/MyComponent.tsx
import { cn } from '@/lib/utils';
interface MyComponentProps {
title: string;
className?: string;
}
export function MyComponent({ title, className }: MyComponentProps) {
return (
<div className={cn('p-4 bg-card rounded-lg', className)}>
<h2 className="text-lg font-semibold">{title}</h2>
</div>
);
}Export from Index
// src/popup/components/index.ts
export { Header } from './Header';
export { Dashboard } from './Dashboard';
export { MyComponent } from './MyComponent';Using Tailwind CSS
// Tailwind classes with conditional styling
<div className={cn(
'rounded-lg p-4',
isActive ? 'bg-primary' : 'bg-muted',
className
)}>Adding a New Data Type
1. Update Types
// src/types/index.ts
export const DataTypeSchema = z.enum([
'browsing_history',
'search_queries',
'social_interactions',
'shopping_behavior',
'content_preferences',
'location_data',
'ad_impressions',
'new_data_type', // Add here
]);2. Add Default Config
// src/types/index.ts
export const DEFAULT_DATA_TYPES: DataTypeConfig[] = [
// ... existing types
{
id: 'new_data_type',
label: 'New Data Type',
description: 'Description of what this tracks',
enabled: false,
estimatedValue: 2.5,
},
];3. Update Collector
// src/lib/collector.ts
export function inferDataType(url: string): DataType {
// Add detection logic for new type
if (isNewDataType(url)) {
return 'new_data_type';
}
// ... existing logic
}4. Update Content Script
// src/content/content-script.ts
if (settings.enabledDataTypes.includes('new_data_type')) {
trackNewDataType();
}API Integration
Adding a New API Endpoint
// src/lib/api.ts
export async function newApiCall(
walletAddress: string,
data: SomeData
): Promise<ApiResponse<ResponseType>> {
try {
const response = await fetch(
`${config.apiUrl}/v1/new-endpoint`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${await getAuthToken()}`,
},
body: JSON.stringify({ walletAddress, data }),
}
);
if (!response.ok) {
return { success: false, data: null, error: 'Request failed' };
}
const result = await response.json();
return { success: true, data: result, error: null };
} catch (error) {
return {
success: false,
data: null,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}Adding a New Message Type
// src/types/index.ts
export const MessageTypeSchema = z.enum([
// ... existing types
'NEW_MESSAGE_TYPE',
]);
// src/background/service-worker.ts
case 'NEW_MESSAGE_TYPE': {
const data = message.payload as NewPayloadType;
return await handleNewMessage(data);
}Build Output
Production Build Contents
dist/
├── manifest.json # Extension manifest
├── background/
│ └── service-worker.js # Compiled service worker
├── content/
│ └── content-script.js # Compiled content script
├── popup.html # Popup HTML
├── popup.js # Popup bundle
├── popup.css # Popup styles
└── icons/
├── icon-16.png
├── icon-32.png
├── icon-48.png
└── icon-128.pngManifest Configuration
{
"manifest_version": 3,
"name": "PRIV Protocol",
"version": "0.1.0",
"minimum_chrome_version": "110",
"background": {
"service_worker": "background/service-worker.js",
"type": "module"
},
"content_scripts": [{
"matches": ["http://*/*", "https://*/*"],
"js": ["content/content-script.js"],
"run_at": "document_idle"
}],
"permissions": ["storage", "activeTab", "tabs", "alarms"]
}Contributing
Pull Request Process
- Fork the repository
- Create a feature branch
- Make your changes
- Run tests and linting
- Submit a pull request
Code Review Checklist
- TypeScript types are correct
- Tests cover new functionality
- No console.log statements (use debugLog)
- Security utilities are used
- Documentation is updated
Next Steps
- Privacy & Security - Security guidelines
- How It Works - Architecture details
- Troubleshooting - Common issues