npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

qevo

v1.0.15

Published

Cross-browser extension toolkit - Unified API for Chrome & Firefox extension development with messaging, storage, webRequest, and tab management

Readme

🚀 Qevo

The Ultimate Cross-Browser Extension Toolkit

Pronounced "keh-vo" • Unified API for Chrome & Firefox Extension Development

TypeScript Chrome Firefox License

Stop fighting browser APIs. Start building extensions.

FeaturesInstallationQuick StartDocumentationExamples


🎯 Why Qevo?

Building browser extensions is hard. Managing Chrome vs Firefox differences is harder. Qevo makes it effortless.

// ❌ Without Qevo - Manual cross-browser handling
const api = typeof browser !== 'undefined' ? browser : chrome;
api.runtime.sendMessage({ type: 'getData' }, (response) => {
  if (api.runtime.lastError) {
    console.error(api.runtime.lastError);
  } else {
    // Handle response
  }
});

// ✅ With Qevo - Clean, modern, type-safe
import qevo from 'qevo';
const response = await qevo.sendMessageToBackground('getData', {});

✨ Core Features

Qevo provides four powerful pillars for extension development:

📨 Intelligent Messaging

Modern async/await communication between all extension contexts:

  • ✅ Background ↔ Content scripts
  • ✅ Tab-to-tab messaging
  • ✅ Broadcast to all tabs
  • ✅ Promise-based with timeout/retry
  • ✅ Type-safe payloads
  • ✅ Event-driven listeners

🗂️ Advanced Storage

Professional-grade storage with features browsers should have:

  • ✅ Automatic TTL (time-to-live)
  • ✅ Expiration dates
  • ✅ Event listeners (add/update/remove)
  • ✅ Batch atomic operations
  • ✅ Prefix/suffix key search
  • ✅ Cross-environment (Chrome/Firefox/IndexedDB)

🌐 WebRequest Mastery

Complete control over HTTP traffic:

  • ✅ All 9 webRequest events
  • ✅ Block/redirect requests
  • ✅ Modify headers
  • ✅ Monitor traffic
  • ✅ Authentication handling
  • ✅ Simple, unified API

📑 Tab Management

Powerful tab control and queries:

  • ✅ Find tabs by URL/title
  • ✅ Query all tabs
  • ✅ Current window/tab detection
  • ✅ Cross-window operations
  • ✅ Rich tab metadata

Plus: Auto-detected debug mode, environment detection, context validation, and full TypeScript support.


📦 Installation

npm install qevo

🚀 Quick Start

1. Import Qevo

import qevo from 'qevo';

// That's it! No initialization, no setup, no config.
// Qevo is ready to use immediately.

2. Send Your First Message

// In background script
qevo.message.on('ping', (data, sender, sendResponse) => {
  sendResponse({ success: true, data: 'pong' });
});

// In content script
const response = await qevo.sendMessageToBackground('ping', {});
console.log(response.data); // "pong"

3. Store Data with Auto-Expiration

// Store session token that expires in 1 hour
await qevo.storage.put('sessionToken', 'abc123', { ttl: 3600 });

// Retrieve it
const token = await qevo.storage.get<string>('sessionToken');

You're now using Qevo! 🎉


📚 Comprehensive API

Qevo's messaging system revolutionizes cross-script communication with a modern, promise-based API that works consistently across Chrome and Firefox.

Core Concepts

Message Flow:

Content Script → Background Script → Another Tab
     ↓                  ↓                ↓
  Sender          Processor         Receiver

Message Types: Unique string identifiers for different message handlers Payloads: Any JSON-serializable data Responses: Type-safe return values with success/error handling


📤 Sending Messages

qevo.sendMessageToBackground<T, R>(messageType, data, options?)

Send a message to the background script and wait for response.

Use Cases:

  • Content script needs data from background
  • Request background to perform action
  • Query background state

Parameters:

messageType: string          // Unique message identifier
data: T                      // Message payload (any JSON data)
options?: {
  timeout?: number           // Max wait time (default: 5000ms)
  retries?: number           // Retry attempts (default: 0)
  retryDelay?: number        // Delay between retries (default: 1000ms)
  expectResponse?: boolean   // Wait for response (default: true)
  priority?: 'low' | 'normal' | 'high'  // Message priority
}

Returns: Promise<MessageResponse<R>>

interface MessageResponse<R> {
  success: boolean;    // Operation succeeded
  data?: R;           // Response data
  error?: string;     // Error message if failed
  timestamp?: number; // Response timestamp
  messageId?: string; // Unique message ID
  duration?: number;  // Round-trip time in ms
}

Example:

// Content script - request user data
const response = await qevo.sendMessageToBackground<
  { userId: number },  // Request type
  { name: string, email: string }  // Response type
>('getUserData', { userId: 123 }, {
  timeout: 10000,
  retries: 2,
  priority: 'high'
});

if (response.success) {
  console.log('User:', response.data.name);
} else {
  console.error('Error:', response.error);
}

qevo.sendMessageToTab<T, R>(tabId, messageType, data, options?)

Send a message to a specific tab's content script.

Use Cases:

  • Background tells content script to update UI
  • Tab-to-tab communication via background
  • Trigger actions in specific tabs

Parameters:

tabId: number               // Target tab ID
messageType: string         // Message identifier
data: T                     // Message payload
options?: MessageOptions    // Same as sendMessageToBackground

Example:

// Background script - tell tab to show notification
const tabs = await qevo.getAllTabs();
for (const tab of tabs) {
  if (tab.url.includes('example.com')) {
    await qevo.sendMessageToTab(tab.tabId, 'showNotification', {
      title: 'Update Available',
      message: 'New version released!',
      type: 'info'
    });
  }
}

qevo.sendMessageToTabs<T>(messageType, data, filter?, options?)

Send a message to multiple tabs matching criteria.

Parameters:

messageType: string
data: T
filter?: {
  urls?: string[]          // URL patterns
  active?: boolean         // Only active tabs
  currentWindow?: boolean  // Only current window
}
options?: MessageOptions

Example:

// Notify all tabs on specific domain
await qevo.sendMessageToTabs('dataUpdated', { timestamp: Date.now() }, {
  urls: ['https://example.com/*']
});

qevo.broadcastMessage<T>(messageType, data, options?)

Broadcast a message to ALL tabs across all windows.

Use Cases:

  • Global state changes
  • System-wide notifications
  • Sync updates across all tabs

Example:

// Background - broadcast theme change to all tabs
await qevo.broadcastMessage('themeChanged', {
  theme: 'dark',
  timestamp: Date.now()
});

📥 Receiving Messages

qevo.message.on<T, R>(messageType, listener)

Register a listener for incoming messages.

Parameters:

messageType: string  // Message type to listen for
listener: (
  data: T,           // Message payload
  sender: MessageSender,  // Sender info (tab, url, etc.)
  sendResponse: (response: MessageResponse<R>) => void
) => void | Promise<void> | boolean | Promise<boolean>

Sender Information:

interface MessageSender {
  tab?: {
    id: number;
    windowId: number;
    url: string;
    title: string;
  };
  url?: string;      // Sender's URL
  origin?: string;   // Sender's origin
}

Response Patterns:

// 1. Synchronous response
qevo.message.on('getData', (data, sender, sendResponse) => {
  sendResponse({ success: true, data: { id: 123 } });
});

// 2. Async response (return true!)
qevo.message.on('fetchUser', async (data, sender, sendResponse) => {
  const user = await fetchUserFromAPI(data.userId);
  sendResponse({ success: true, data: user });
  return true; // IMPORTANT: Keeps channel open for async
});

// 3. No response expected
qevo.message.on('logEvent', (data) => {
  console.log('Event logged:', data);
  // No sendResponse call
});

Example - Complex Handler:

interface AuthRequest {
  username: string;
  password: string;
}

interface AuthResponse {
  token: string;
  expiresAt: Date;
  user: { id: number; name: string };
}

qevo.message.on<AuthRequest, AuthResponse>(
  'authenticate',
  async (data, sender, sendResponse) => {
    try {
      // Validate input
      if (!data.username || !data.password) {
        sendResponse({
          success: false,
          error: 'Username and password required'
        });
        return true;
      }

      // Perform authentication
      const authResult = await authenticateUser(
        data.username,
        data.password
      );

      // Store token
      await qevo.storage.put('authToken', authResult.token, {
        ttl: 3600
      });

      // Send success response
      sendResponse({
        success: true,
        data: authResult,
        timestamp: Date.now()
      });
    } catch (error) {
      sendResponse({
        success: false,
        error: error.message
      });
    }
    return true; // Keep channel open for async
  }
);

qevo.message.off<T, R>(messageType, listener)

Remove a specific message listener.

Example:

const handler = (data, sender, sendResponse) => { ... };

qevo.message.on('myEvent', handler);

// Later... remove it
qevo.message.off('myEvent', handler);

qevo.message.clear(messageType)

Remove ALL listeners for a message type.

Example:

qevo.message.clear('userData');  // Removes all 'userData' listeners

🔍 Message API Utilities

qevo.message.getTypes()

Get all registered message types.

Returns: string[]

Example:

const types = qevo.message.getTypes();
console.log('Registered handlers:', types);
// ['getUserData', 'authenticate', 'updateSettings', ...]

qevo.message.hasListeners(messageType)

Check if there are listeners for a message type.

Returns: boolean

Example:

if (!qevo.message.hasListeners('getData')) {
  console.warn('No handler registered for getData');
}

💡 Messaging Best Practices

1. Always Handle Errors

try {
  const response = await qevo.sendMessageToTab(tabId, 'getData', {});
  if (!response.success) {
    console.error('Operation failed:', response.error);
  }
} catch (error) {
  console.error('Message delivery failed:', error);
}

2. Use Timeouts for Unreliable Operations

const response = await qevo.sendMessageToBackground('syncData', data, {
  timeout: 30000,  // 30 seconds for slow operations
  retries: 2       // Retry twice if fails
});

3. Return true for Async Handlers

// ✅ Correct
qevo.message.on('asyncOp', async (data, sender, sendResponse) => {
  await someAsyncOperation();
  sendResponse({ success: true });
  return true;  // REQUIRED!
});

// ❌ Wrong - response channel closes immediately
qevo.message.on('asyncOp', async (data, sender, sendResponse) => {
  await someAsyncOperation();
  sendResponse({ success: true });
  // Missing return true!
});

4. Use TypeScript for Type Safety

interface GetUserRequest {
  userId: number;
}

interface GetUserResponse {
  id: number;
  name: string;
  email: string;
}

// Sender
const response = await qevo.sendMessageToBackground<
  GetUserRequest,
  GetUserResponse
>('getUser', { userId: 123 });

// Receiver
qevo.message.on<GetUserRequest, GetUserResponse>(
  'getUser',
  async (data, sender, sendResponse) => {
    const user = await fetchUser(data.userId);
    sendResponse({ success: true, data: user });
    return true;
  }
);

Qevo's storage system (qevo.storage) goes far beyond basic key-value storage. It provides enterprise-grade features that browsers should have had from the start.

Core Concepts

Automatic Backend Selection:

  • Chrome Extension: chrome.storage.local (~10MB)
  • Firefox Extension: browser.storage.local (~10MB)
  • Web/Worker Context: IndexedDB (~50MB+)

Key Features:

  • TTL (Time-To-Live): Auto-delete after X seconds
  • 📅 Expiration Dates: Delete at specific time
  • 🔔 Event Listeners: React to add/update/remove
  • Batch Operations: Atomic multi-operation transactions
  • 🔍 Key Search: Prefix/suffix matching
  • 🧹 Auto Cleanup: Background cleanup of expired items

💾 Basic Operations

qevo.storage.put<T>(key, value, options?)

Store a value with optional expiration.

Parameters:

key: string                    // Storage key
value: T                       // Any JSON-serializable data
options?: {
  ttl?: number                 // Time-to-live in SECONDS
  expires?: Date               // Absolute expiration date
}

Returns: Promise<void>

Examples:

// 1. Simple storage
await qevo.storage.put('username', 'john_doe');

// 2. With TTL (auto-delete after 5 minutes)
await qevo.storage.put('sessionToken', 'abc123', { ttl: 300 });

// 3. With expiration date
await qevo.storage.put('subscription', {
  plan: 'premium',
  features: ['feature1', 'feature2']
}, {
  expires: new Date('2025-12-31T23:59:59Z')
});

// 4. Complex objects
await qevo.storage.put('userSettings', {
  theme: 'dark',
  language: 'en',
  notifications: {
    email: true,
    push: false,
    sms: true
  }
}, { ttl: 86400 }); // 24 hours

qevo.storage.get<T>(key, usePrefix?)

Retrieve a value by key.

Parameters:

key: string              // Storage key
usePrefix?: boolean      // If true, finds first key starting with `key`

Returns: Promise<T | null>

  • Returns the value if found and not expired
  • Returns null if not found or expired
  • Automatically removes expired items

Examples:

// 1. Simple retrieval
const username = await qevo.storage.get<string>('username');
if (username) {
  console.log('Welcome back,', username);
}

// 2. Complex objects with type safety
interface UserSettings {
  theme: 'light' | 'dark';
  language: string;
  notifications: {
    email: boolean;
    push: boolean;
    sms: boolean;
  };
}

const settings = await qevo.storage.get<UserSettings>('userSettings');
if (settings) {
  applyTheme(settings.theme);
  setLanguage(settings.language);
}

// 3. Prefix matching - gets first key starting with 'session_'
const firstSession = await qevo.storage.get('session_', true);

// 4. With default fallback
const theme = await qevo.storage.get<string>('theme') || 'light';

qevo.storage.has(key)

Check if a key exists (and is not expired).

Returns: Promise<boolean>

Example:

if (await qevo.storage.has('authToken')) {
  // User is authenticated
  loadDashboard();
} else {
  // User needs to login
  showLoginForm();
}

qevo.storage.remove(key)

Remove a key from storage.

Returns: Promise<void>

Example:

// User logout
await qevo.storage.remove('authToken');
await qevo.storage.remove('userSession');
await qevo.storage.remove('userData');

🔍 Advanced Queries

qevo.storage.listKeys(prefix?)

List all keys, optionally filtered by prefix.

Parameters:

prefix?: string  // Optional prefix to filter keys

Returns: Promise<string[]>

Examples:

// 1. Get all keys
const allKeys = await qevo.storage.listKeys();
console.log('Total items:', allKeys.length);

// 2. Get all session-related keys
const sessionKeys = await qevo.storage.listKeys('session_');
// Returns: ['session_token', 'session_user', 'session_expires']

// 3. Get all cache keys
const cacheKeys = await qevo.storage.listKeys('cache_');
for (const key of cacheKeys) {
  await qevo.storage.remove(key); // Clear all cache
}

// 4. Find all temporary data
const tempKeys = await qevo.storage.listKeys('temp_');
console.log('Temporary items:', tempKeys);

qevo.storage.getKeyByPrefix(prefix)

Get the first key that starts with prefix.

Returns: Promise<string | null>

Example:

const sessionKey = await qevo.storage.getKeyByPrefix('session_');
if (sessionKey) {
  const value = await qevo.storage.get(sessionKey);
}

qevo.storage.getKeyBySuffix(suffix)

Get the first key that ends with suffix.

Returns: Promise<string | null>

Example:

const tokenKey = await qevo.storage.getKeyBySuffix('_token');
// Finds: 'auth_token', 'api_token', etc.

⚡ Batch Operations

qevo.storage.batch(operations)

Execute multiple operations atomically in a single transaction.

Operations:

type BatchOperation =
  | { type: 'set'; key: string; value: any; ttl?: number; expires?: Date }
  | { type: 'get'; key: string }
  | { type: 'remove'; key: string };

Returns: Promise<(any | null | undefined)[]>

  • Array of results corresponding to each operation
  • set operations return undefined
  • get operations return the value or null
  • remove operations return undefined

Example:

// Initialize user session with multiple related items
const results = await qevo.storage.batch([
  // Set auth token (expires in 1 hour)
  {
    type: 'set',
    key: 'auth_token',
    value: 'abc123xyz',
    ttl: 3600
  },
  // Set user data (expires in 1 hour)
  {
    type: 'set',
    key: 'user_data',
    value: {
      id: 123,
      name: 'John Doe',
      email: '[email protected]'
    },
    ttl: 3600
  },
  // Set preferences (permanent)
  {
    type: 'set',
    key: 'user_preferences',
    value: {
      theme: 'dark',
      language: 'en'
    }
  },
  // Remove old session if exists
  {
    type: 'remove',
    key: 'old_session'
  },
  // Get existing cart data
  {
    type: 'get',
    key: 'shopping_cart'
  }
]);

const cartData = results[4]; // Result from the get operation

Benefits:

  • ✅ Atomic - all operations succeed or all fail
  • ✅ Faster than individual operations
  • ✅ Consistent state - no partial updates

🔔 Event Listeners

Listen to storage changes in real-time across all extension contexts.

qevo.storage.addListener(type, callback)

Register a listener for storage events.

Event Types:

  • 'add' - New item added
  • 'update' - Existing item modified
  • 'remove' - Item removed

Callbacks:

// Add event
(key: string, value: any) => void

// Update event
(key: string, oldValue: any, newValue: any) => void

// Remove event
(key: string) => void

Examples:

// 1. Monitor authentication changes
qevo.storage.addListener('add', (key, value) => {
  if (key === 'authToken') {
    console.log('User logged in');
    updateUIToAuthenticatedState();
  }
});

qevo.storage.addListener('remove', (key) => {
  if (key === 'authToken') {
    console.log('User logged out');
    redirectToLogin();
  }
});

qevo.storage.addListener('update', (key, oldValue, newValue) => {
  if (key === 'userSettings') {
    console.log('Settings changed');
    applyNewSettings(newValue);
  }
});

// 2. Track all storage changes (debugging)
qevo.storage.addListener('add', (key, value) => {
  console.log('Added:', key, value);
});

qevo.storage.addListener('update', (key, old, neu) => {
  console.log('Updated:', key, 'from', old, 'to', neu);
});

qevo.storage.addListener('remove', (key) => {
  console.log('Removed:', key);
});

// 3. Sync across tabs
qevo.storage.addListener('update', (key, oldValue, newValue) => {
  if (key === 'cart_items') {
    // Notify other tabs about cart changes
    qevo.broadcastMessage('cartUpdated', {
      items: newValue,
      timestamp: Date.now()
    });
  }
});

qevo.storage.removeListener(type, callback)

Remove a specific listener.

Example:

const handler = (key, value) => console.log(key, value);

qevo.storage.addListener('add', handler);

// Later...
qevo.storage.removeListener('add', handler);

📊 Storage Metadata

qevo.storage.length()

Get the number of items in storage.

Returns: Promise<number>

Example:

const count = await qevo.storage.length();
console.log(`You have ${count} items stored`);

if (count > 1000) {
  console.warn('Storage is getting full, consider cleanup');
}

qevo.storage.getStorageUsage()

Get current storage usage in bytes.

Returns: Promise<number>

Example:

const bytes = await qevo.storage.getStorageUsage();
const mb = (bytes / 1024 / 1024).toFixed(2);

console.log(`Using ${mb} MB of storage`);

// Check quota (Chrome: ~10MB)
const QUOTA_MB = 10;
const usagePercent = (bytes / (QUOTA_MB * 1024 * 1024)) * 100;

if (usagePercent > 80) {
  alert(`Storage ${usagePercent.toFixed(1)}% full!`);
}

💡 Storage Best Practices

1. Use TTL for Temporary Data

// ✅ Good - auto-cleanup
await qevo.storage.put('apiCache', data, { ttl: 300 });

// ❌ Bad - manual cleanup required
await qevo.storage.put('apiCache', data);
setTimeout(() => qevo.storage.remove('apiCache'), 300000);

2. Namespace Your Keys

// ✅ Good - organized
await qevo.storage.put('auth_token', token);
await qevo.storage.put('auth_user', user);
await qevo.storage.put('cache_users', users);
await qevo.storage.put('cache_posts', posts);

// ❌ Bad - hard to manage
await qevo.storage.put('token', token);
await qevo.storage.put('user', user);
await qevo.storage.put('users', users);

3. Use Batch for Related Data

// ✅ Good - atomic transaction
await qevo.storage.batch([
  { type: 'set', key: 'user_id', value: 123 },
  { type: 'set', key: 'user_name', value: 'John' },
  { type: 'set', key: 'user_email', value: '[email protected]' }
]);

// ❌ Bad - can result in partial state
await qevo.storage.put('user_id', 123);
await qevo.storage.put('user_name', 'John');
// If fails here, id and name saved but no email
await qevo.storage.put('user_email', '[email protected]');

4. Handle Expiration Gracefully

const token = await qevo.storage.get<string>('authToken');
if (!token) {
  // Token expired or doesn't exist
  // Redirect to login or refresh token
  await refreshAuthToken();
}

⚠️ Background/Service Worker Only - This API only works in background scripts, not content scripts.

Qevo's WebRequest API provides complete control over HTTP traffic with a simplified, type-safe interface.

Core Concepts

Request Lifecycle:

BeforeRequest → BeforeSendHeaders → SendHeaders → HeadersReceived →
ResponseStarted → Completed/ErrorOccurred

         ↓ Can redirect/cancel
    BeforeRedirect
         ↓ Auth required?
    AuthRequired

Event Categories:

  • Blocking Events: Can modify/cancel requests
  • Observing Events: Monitor-only, cannot modify

🎯 Event Types

'BeforeRequest' (Blocking)

Fired before request is sent. Can cancel or redirect.

Use Cases:

  • Block ads/trackers
  • Redirect requests
  • Log request attempts

Example:

qevo.webRequest.on('BeforeRequest',
  (details) => {
    console.log('Request to:', details.url);
    console.log('Method:', details.method);
    console.log('Type:', details.type);

    // Block tracking scripts
    if (details.url.includes('analytics')) {
      return { cancel: true };
    }

    // Redirect old API to new API
    if (details.url.includes('api.old.com')) {
      return {
        redirectUrl: details.url.replace('api.old.com', 'api.new.com')
      };
    }
  },
  { urls: ['<all_urls>'] },
  ['blocking']
);

'BeforeSendHeaders' (Blocking)

Fired before headers are sent. Can modify request headers.

Use Cases:

  • Add authentication headers
  • Modify user agent
  • Add custom headers

Example:

qevo.webRequest.on('BeforeSendHeaders',
  (details) => {
    const headers = details.requestHeaders || [];

    // Add authorization header
    headers.push({
      name: 'Authorization',
      value: 'Bearer abc123xyz'
    });

    // Add custom header
    headers.push({
      name: 'X-Custom-Client',
      value: 'MyExtension/1.0'
    });

    // Modify existing header
    const userAgentIndex = headers.findIndex(
      h => h.name.toLowerCase() === 'user-agent'
    );
    if (userAgentIndex >= 0) {
      headers[userAgentIndex].value = 'CustomBot/1.0';
    }

    return { requestHeaders: headers };
  },
  { urls: ['https://api.example.com/*'] },
  ['blocking', 'requestHeaders']
);

'SendHeaders' (Observing)

Fired when headers are sent. Monitor-only.

Use Cases:

  • Log outgoing requests
  • Monitor request headers
  • Debug network issues

Example:

qevo.webRequest.on('SendHeaders',
  (details) => {
    console.log('Sending request to:', details.url);
    console.log('Headers:', details.requestHeaders);

    // Log API calls
    if (details.url.includes('/api/')) {
      logAPICall({
        url: details.url,
        method: details.method,
        timestamp: details.timeStamp
      });
    }
  },
  { urls: ['<all_urls>'] },
  ['requestHeaders']
);

'HeadersReceived' (Blocking)

Fired when response headers received. Can modify response headers.

Use Cases:

  • Modify CORS headers
  • Change content type
  • Add security headers

Example:

qevo.webRequest.on('HeadersReceived',
  (details) => {
    const headers = details.responseHeaders || [];

    // Add CORS header
    headers.push({
      name: 'Access-Control-Allow-Origin',
      value: '*'
    });

    // Force content type
    const ctIndex = headers.findIndex(
      h => h.name.toLowerCase() === 'content-type'
    );
    if (ctIndex >= 0) {
      headers[ctIndex].value = 'application/json';
    }

    return {
      responseHeaders: headers,
      statusCode: details.statusCode
    };
  },
  { urls: ['<all_urls>'] },
  ['blocking', 'responseHeaders']
);

'AuthRequired' (Blocking)

Fired when authentication required.

Use Cases:

  • Provide credentials
  • Handle proxy auth
  • Custom auth flows

Example:

qevo.webRequest.on('AuthRequired',
  (details) => {
    return {
      authCredentials: {
        username: 'user',
        password: 'pass'
      }
    };
  },
  { urls: ['<all_urls>'] },
  ['blocking']
);

'ResponseStarted' (Observing)

Fired when response starts. Monitor-only.

Example:

qevo.webRequest.on('ResponseStarted',
  (details) => {
    console.log('Response started:', details.url);
    console.log('Status:', details.statusCode);
    console.log('Headers:', details.responseHeaders);
  },
  { urls: ['<all_urls>'] },
  ['responseHeaders']
);

'BeforeRedirect' (Observing)

Fired before redirect happens. Monitor-only.

Example:

qevo.webRequest.on('BeforeRedirect',
  (details) => {
    console.log('Redirecting from:', details.url);
    console.log('Redirecting to:', details.redirectUrl);
  },
  { urls: ['<all_urls>'] }
);

'Completed' (Observing)

Fired when request completes successfully.

Example:

const requestTimes = new Map();

qevo.webRequest.on('BeforeRequest', (details) => {
  requestTimes.set(details.requestId, Date.now());
}, { urls: ['<all_urls>'] });

qevo.webRequest.on('Completed', (details) => {
  const startTime = requestTimes.get(details.requestId);
  if (startTime) {
    const duration = Date.now() - startTime;
    console.log(`Request took ${duration}ms:`, details.url);
    requestTimes.delete(details.requestId);
  }
}, { urls: ['<all_urls>'] });

'ErrorOccurred' (Observing)

Fired when request fails.

Example:

qevo.webRequest.on('ErrorOccurred',
  (details) => {
    console.error('Request failed:', details.url);
    console.error('Error:', details.error);

    // Log failed API calls
    if (details.url.includes('/api/')) {
      logAPIError({
        url: details.url,
        error: details.error,
        timestamp: details.timeStamp
      });
    }
  },
  { urls: ['<all_urls>'] }
);

🎛️ Filters

Control which requests trigger your listeners.

interface WebRequestFilter {
  urls: string[];              // URL patterns (required)
  types?: ResourceType[];      // Request types
  tabId?: number;              // Specific tab
  windowId?: number;           // Specific window
}

// Resource types
type ResourceType =
  | 'main_frame'      // Top-level document
  | 'sub_frame'       // iframe
  | 'stylesheet'      // CSS
  | 'script'          // JavaScript
  | 'image'           // Images
  | 'font'            // Fonts
  | 'object'          // Plugins
  | 'xmlhttprequest'  // XHR/fetch
  | 'ping'            // Ping
  | 'csp_report'      // CSP reports
  | 'media'           // Audio/video
  | 'websocket'       // WebSocket
  | 'other';

Examples:

// 1. All requests
{ urls: ['<all_urls>'] }

// 2. Specific domain
{ urls: ['https://example.com/*'] }

// 3. Multiple domains
{ urls: [
  'https://api.example.com/*',
  'https://cdn.example.com/*'
]}

// 4. Only images
{
  urls: ['<all_urls>'],
  types: ['image']
}

// 5. Only XHR/fetch from specific domain
{
  urls: ['https://api.example.com/*'],
  types: ['xmlhttprequest']
}

// 6. Specific tab
{
  urls: ['<all_urls>'],
  tabId: 123
}

📌 Extra Info Specs

Specify what additional data you need.

type ExtraInfoSpec =
  | 'blocking'           // Can modify request
  | 'requestHeaders'     // Include request headers
  | 'responseHeaders'    // Include response headers
  | 'extraHeaders';      // Include extra headers

Examples:

// Monitor headers
qevo.webRequest.on('SendHeaders', handler,
  { urls: ['<all_urls>'] },
  ['requestHeaders']
);

// Modify request
qevo.webRequest.on('BeforeRequest', handler,
  { urls: ['<all_urls>'] },
  ['blocking']
);

// Modify headers
qevo.webRequest.on('BeforeSendHeaders', handler,
  { urls: ['<all_urls>'] },
  ['blocking', 'requestHeaders', 'extraHeaders']
);

🛠️ WebRequest Utilities

qevo.webRequest.off(eventType, listener)

Remove a listener.

Example:

const handler = (details) => { ... };

qevo.webRequest.on('SendHeaders', handler, { urls: ['<all_urls>'] });

// Later...
qevo.webRequest.off('SendHeaders', handler);

qevo.webRequest.clear(eventType)

Remove all listeners for an event type.

Example:

qevo.webRequest.clear('SendHeaders');

qevo.webRequest.isAvailable()

Check if WebRequest API is available (background script check).

Returns: boolean


💡 WebRequest Best Practices

1. Use Specific Filters

// ✅ Good - specific
qevo.webRequest.on('SendHeaders', handler, {
  urls: ['https://api.example.com/*'],
  types: ['xmlhttprequest']
}, ['requestHeaders']);

// ❌ Bad - all requests
qevo.webRequest.on('SendHeaders', handler, {
  urls: ['<all_urls>']
}, ['requestHeaders']);

2. Only Request Data You Need

// ✅ Good - minimal
qevo.webRequest.on('SendHeaders', handler,
  { urls: ['<all_urls>'] }
);

// ❌ Bad - unnecessary data
qevo.webRequest.on('SendHeaders', handler,
  { urls: ['<all_urls>'] },
  ['requestHeaders', 'responseHeaders', 'extraHeaders']
);

3. Handle Errors in Blocking Listeners

qevo.webRequest.on('BeforeRequest', (details) => {
  try {
    // Your logic
    if (shouldBlock(details.url)) {
      return { cancel: true };
    }
  } catch (error) {
    console.error('Error in webRequest handler:', error);
    // Don't block on error
    return {};
  }
}, { urls: ['<all_urls>'] }, ['blocking']);

Powerful tab querying and management capabilities.

📋 Query Tabs

qevo.getAllTabs()

Get all tabs across all windows.

Returns: Promise<TabInfo[]>

TabInfo Interface:

interface TabInfo {
  tabId: number;
  windowId: number;
  url: string;
  title: string;
  active: boolean;
  pinned: boolean;
  audible: boolean;
  muted: boolean;
  incognito: boolean;
  status: 'loading' | 'complete';
  favIconUrl?: string;
  index: number;
  isInCurrentWindow: boolean;
  isCurrentTab: boolean;
}

Example:

const tabs = await qevo.getAllTabs();

// Find YouTube tabs
const youtubeTabs = tabs.filter(tab =>
  tab.url.includes('youtube.com')
);

// Get all loading tabs
const loadingTabs = tabs.filter(tab =>
  tab.status === 'loading'
);

// Count tabs per window
const tabsByWindow = tabs.reduce((acc, tab) => {
  acc[tab.windowId] = (acc[tab.windowId] || 0) + 1;
  return acc;
}, {});

qevo.getTabsInCurrentWindow()

Get all tabs in the current window.

Returns: Promise<TabInfo[]>

Example:

const tabs = await qevo.getTabsInCurrentWindow();
console.log(`Current window has ${tabs.length} tabs`);

qevo.getCurrentTab()

Get the currently active tab.

Returns: Promise<TabInfo | null>

Example:

const currentTab = await qevo.getCurrentTab();
if (currentTab) {
  console.log('Current tab:', currentTab.title);
  console.log('URL:', currentTab.url);
}

🔍 Find Tabs

qevo.getTabByUrl(url, exactMatch?)

Find a tab by URL.

Parameters:

url: string           // URL to search for
exactMatch?: boolean  // true = exact match, false = partial match (default: false)

Returns: Promise<TabInfo | null>

Examples:

// Partial match (default)
const githubTab = await qevo.getTabByUrl('github.com');

// Exact match
const specificTab = await qevo.getTabByUrl(
  'https://github.com/user/repo',
  true
);

// Check if tab exists
if (await qevo.getTabByUrl('example.com')) {
  console.log('Example.com is open');
}

qevo.getTabById(tabId)

Get tab by ID.

Returns: Promise<TabInfo | null>

Example:

const tab = await qevo.getTabById(123);
if (tab) {
  console.log('Tab found:', tab.title);
} else {
  console.log('Tab no longer exists');
}

qevo.getTabByTitle(title, exactMatch?)

Find a tab by title.

Parameters:

title: string         // Title to search for
exactMatch?: boolean  // true = exact match, false = partial match

Returns: Promise<TabInfo | null>

Examples:

// Partial match
const tab = await qevo.getTabByTitle('GitHub');

// Exact match
const specificTab = await qevo.getTabByTitle(
  'GitHub - Where the world builds software',
  true
);

💡 Tab Management Best Practices

1. Cache Tab Queries

// ✅ Good
const tabs = await qevo.getAllTabs();
const githubTabs = tabs.filter(t => t.url.includes('github.com'));
const youtubeTabs = tabs.filter(t => t.url.includes('youtube.com'));

// ❌ Bad
const githubTabs = await qevo.getAllTabs().then(tabs =>
  tabs.filter(t => t.url.includes('github.com'))
);
const youtubeTabs = await qevo.getAllTabs().then(tabs =>
  tabs.filter(t => t.url.includes('youtube.com'))
);

2. Check for Null Results

const tab = await qevo.getTabByUrl('example.com');
if (tab) {
  await qevo.sendMessageToTab(tab.tabId, 'update', {});
} else {
  console.warn('Tab not found');
}

Environment Detection

qevo.isBackgroundScript()

Check if code is running in background/service worker.

Returns: boolean

Example:

if (qevo.isBackgroundScript()) {
  // Initialize background-only features
  qevo.webRequest.on('SendHeaders', ...);
} else {
  console.log('Running in content script or popup');
}

qevo.isContentScript()

Check if code is running in content script.

Returns: boolean

Example:

if (qevo.isContentScript()) {
  // Content script specific code
  document.addEventListener('click', handler);
}

qevo.getBrowserType()

Get current browser type.

Returns: 'chrome' | 'firefox'

Example:

const browser = qevo.getBrowserType();

if (browser === 'firefox') {
  // Firefox-specific workarounds
} else {
  // Chrome behavior
}

Debug Mode

qevo.debug

Enable/disable verbose logging.

Type: boolean (getter/setter)

Example:

// Enable debug mode
qevo.debug = true;

// Check status
if (qevo.debug) {
  console.log('Debug mode enabled');
}

// Disable
qevo.debug = false;

Auto-Detection: Qevo automatically detects debug mode from:

  • process.env.NODE_ENV !== 'production'
  • import.meta.env.DEV === true
  • Vite/Webpack/bundler dev mode

🎨 Real-World Examples

Example 1: Authentication System

// background.ts
import qevo from 'qevo';

// Listen for login
qevo.message.on('login', async (data: { username: string; password: string }, sender, sendResponse) => {
  try {
    // Authenticate
    const response = await fetch('https://api.example.com/auth', {
      method: 'POST',
      body: JSON.stringify(data)
    });

    const { token, user } = await response.json();

    // Store token with 1 hour TTL
    await qevo.storage.put('authToken', token, { ttl: 3600 });
    await qevo.storage.put('currentUser', user, { ttl: 3600 });

    // Notify all tabs
    await qevo.broadcastMessage('authChanged', { authenticated: true, user });

    sendResponse({ success: true, data: user });
  } catch (error) {
    sendResponse({ success: false, error: error.message });
  }
  return true;
});

// Auto-logout on token expiration
qevo.storage.addListener('remove', (key) => {
  if (key === 'authToken') {
    qevo.broadcastMessage('authChanged', { authenticated: false });
  }
});
// content.ts
import qevo from 'qevo';

// Handle auth state changes
qevo.message.on('authChanged', (data) => {
  if (data.authenticated) {
    showWelcomeMessage(data.user.name);
  } else {
    redirectToLogin();
  }
});

// Login form handler
document.getElementById('loginForm').addEventListener('submit', async (e) => {
  e.preventDefault();

  const response = await qevo.sendMessageToBackground('login', {
    username: usernameInput.value,
    password: passwordInput.value
  });

  if (response.success) {
    console.log('Logged in:', response.data.name);
  } else {
    showError(response.error);
  }
});

Example 2: Request Monitoring Dashboard

// background.ts
interface RequestMetrics {
  url: string;
  method: string;
  duration: number;
  statusCode: number;
  timestamp: number;
}

const activeRequests = new Map<string, number>();
const metrics: RequestMetrics[] = [];

// Track request start
qevo.webRequest.on('BeforeRequest', (details) => {
  activeRequests.set(details.requestId, Date.now());
}, { urls: ['<all_urls>'] });

// Track request completion
qevo.webRequest.on('Completed', (details) => {
  const startTime = activeRequests.get(details.requestId);
  if (startTime) {
    const metric: RequestMetrics = {
      url: details.url,
      method: details.method,
      duration: Date.now() - startTime,
      statusCode: details.statusCode || 200,
      timestamp: Date.now()
    };

    metrics.push(metric);

    // Store last 100 metrics
    if (metrics.length > 100) {
      metrics.shift();
    }

    // Update dashboard
    qevo.sendMessageToTabs('metricsUpdated', metrics, {
      urls: ['*://*/dashboard.html']
    });

    activeRequests.delete(details.requestId);
  }
}, { urls: ['<all_urls>'] });

// Provide metrics to popup
qevo.message.on('getMetrics', (data, sender, sendResponse) => {
  sendResponse({ success: true, data: metrics });
});

Example 3: Multi-Tab Sync

// Sync form data across tabs
import qevo from 'qevo';

// Save form data with debouncing
let saveTimeout: number;

document.querySelectorAll('input, textarea').forEach(input => {
  input.addEventListener('input', () => {
    clearTimeout(saveTimeout);
    saveTimeout = setTimeout(async () => {
      const formData = collectFormData();
      await qevo.storage.put('formDraft', formData, { ttl: 86400 });

      // Notify other tabs
      qevo.broadcastMessage('formDataChanged', formData);
    }, 500);
  });
});

// Listen for changes from other tabs
qevo.message.on('formDataChanged', (data) => {
  if (confirm('Form data changed in another tab. Load latest version?')) {
    populateForm(data);
  }
});

// Load saved data on page load
window.addEventListener('load', async () => {
  const saved = await qevo.storage.get('formDraft');
  if (saved) {
    populateForm(saved);
  }
});

🏗️ TypeScript Support

Qevo is built with TypeScript and provides complete type safety.

// Define your message types
interface GetUserRequest {
  userId: number;
}

interface GetUserResponse {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

// Type-safe message sending
const response = await qevo.sendMessageToBackground<
  GetUserRequest,
  GetUserResponse
>('getUser', { userId: 123 });

if (response.success && response.data) {
  const user: GetUserResponse = response.data;
  console.log(user.name); // ✅ Type-safe
  console.log(user.age);  // ❌ TypeScript error
}

// Type-safe message handling
qevo.message.on<GetUserRequest, GetUserResponse>(
  'getUser',
  async (data, sender, sendResponse) => {
    const userId = data.userId; // ✅ Type-safe

    const user = await fetchUser(userId);

    sendResponse({
      success: true,
      data: user // ✅ Must match GetUserResponse
    });
    return true;
  }
);

// Type-safe storage
interface UserSettings {
  theme: 'light' | 'dark';
  notifications: boolean;
  language: string;
}

await qevo.storage.put<UserSettings>('settings', {
  theme: 'dark',
  notifications: true,
  language: 'en'
});

const settings = await qevo.storage.get<UserSettings>('settings');
if (settings) {
  console.log(settings.theme); // ✅ 'light' | 'dark'
}

🎯 Best Practices Summary

✅ Do's

  1. Always use TypeScript types
  2. Use TTL for temporary data
  3. Handle errors properly with try/catch
  4. Use batch operations for related data
  5. Namespace your storage keys
  6. Return true in async message handlers
  7. Use specific filters in webRequest
  8. Check for null results from queries
  9. Clean up listeners when done
  10. Use debug mode during development

❌ Don'ts

  1. Don't use webRequest in content scripts
  2. Don't forget to await async operations
  3. Don't store sensitive data without encryption
  4. Don't create memory leaks with listeners
  5. Don't use <all_urls> without good reason
  6. Don't ignore error responses
  7. Don't use sync storage APIs manually
  8. Don't forget TTL for temporary data
  9. Don't make redundant tab queries
  10. Don't block main thread with heavy operations

📞 Support & Resources

  • 📚 Documentation: Full API reference (this README)
  • 🐛 Issues: Report bugs or request features
  • 💬 Community: Join discussions
  • GitHub: Star if you find this useful!

📄 License

MIT License - Free to use in personal and commercial projects


Built with ❤️ for the browser extension community

Making extension development delightful, one API at a time.

Qevo • Cross-Browser • Type-Safe • Production-Ready