PRIV ProtocolPRIV Docs
Plugin

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.ts

Prerequisites

RequirementVersion
Node.js18+
npm9+
Chrome110+

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 higher

Build Commands

Install Dependencies

cd extensions/chrome
npm install

Development Build

Build with watch mode for development:

npm run dev

This 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:prod

This command:

  • Runs TypeScript type checking
  • Generates extension icons
  • Builds minified output
  • Excludes source maps
  • Sets production API endpoints

All Available Scripts

CommandDescription
npm run devDevelopment build with watch
npm run buildStandard build
npm run build:prodProduction build
npm run typecheckTypeScript type checking
npm run lintESLint code linting
npm run testRun test suite
npm run test:watchRun tests in watch mode
npm run test:coverageRun tests with coverage
npm run iconsGenerate extension icons
npm run cleanRemove 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

VariableDevelopmentProduction
API URLStaging serverProduction server
Debug logsEnabledDisabled
Source mapsIncludedExcluded
MinificationDisabledEnabled

Loading the Extension

Load Unpacked Extension

  1. Run the build:

    npm run build
  2. Open Chrome and navigate to chrome://extensions

  3. Enable Developer mode (top right toggle)

  4. Click Load unpacked

  5. Select the extensions/chrome/dist folder

[Screenshot: Chrome extensions page with Developer mode enabled]

Reload After Changes

After rebuilding:

  1. Go to chrome://extensions
  2. Find PRIV Protocol
  3. Click the refresh icon

Or use the keyboard shortcut (if enabled in Chrome).


Debugging

Service Worker Debugging

Debug the background service worker:

  1. Go to chrome://extensions
  2. Find PRIV Protocol
  3. Click Inspect views: service worker
  4. DevTools opens for the service worker

[Screenshot: Service worker DevTools console]

Content Script Debugging

Debug content scripts on a page:

  1. Open DevTools on any webpage (F12)
  2. Go to Sources tab
  3. Find content-scripts in the file tree
  4. Set breakpoints in content-script.js

Debug the popup UI:

  1. Right-click the extension icon
  2. Select Inspect popup
  3. 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:coverage

Test 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('&lt;script&gt;');
    expect(escapeHtml('"test"')).toBe('&quot;test&quot;');
  });
});

Coverage Report

npm run test:coverage

Coverage 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 -- --fix

Import 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.png

Manifest 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

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Run tests and linting
  5. 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