Echo is a powerful Chrome extension that intercepts HTTP requests (fetch and XMLHttpRequest) in web pages, enabling developers to:
- Mock API responses without modifying application code
- Modify request/response data on-the-fly for testing edge cases
- Simulate network conditions with configurable delays
- Test error scenarios by injecting custom status codes
- Work offline with mocked responses when APIs are unavailable
Perfect for frontend development, API testing, debugging production issues, and rapid prototyping.
- Features
- Architecture
- Installation
- Quick Start
- Usage Guide
- Development
- Building
- Project Structure
- Technical Details
- Troubleshooting
- Contributing
- License
- 🎭 Request Interception: Captures all
fetchandXMLHttpRequestcalls in web pages - 🔧 Mock Responses: Return custom JSON/text responses without hitting real APIs
- ✏️ Request/Response Modification: Patch headers, body, or status codes
- 🎯 Advanced Matching: Filter requests by URL (exact, contains, regex) and HTTP method
- 📝 JSON Deep Merge: Intelligently merge response bodies without replacing entire payloads
- ⏱️ Delay Simulation: Add configurable delays to simulate network latency
- 💾 Persistent Storage: Rules stored in IndexedDB with instant sync
- 📦 Import/Export: Share rules via JSON files
- 🔄 Rule Ordering: First-match-wins evaluation with drag-and-drop reordering
- 🚦 Master Toggle: Enable/disable all rules with a single switch
- 🔍 Request Simulator: Test rules without leaving the extension
- Modern React 19 + TypeScript implementation
- Tailwind CSS with shadcn/ui components
- CodeMirror-powered JSON editor with syntax highlighting
- Real-time validation and error handling
- Responsive design optimized for the extension popup
Echo uses a multi-layer architecture to ensure reliable request interception:
┌─────────────────────────────────────────────────────────┐
│ Web Page (DOM) │
│ ┌───────────────────────────────────────────────────┐ │
│ │ fetch() / XMLHttpRequest (Intercepted) │ │
│ └───────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌───────────────▼───────────────────────────────────┐ │
│ │ Page Interceptor (MAIN world) │ │
│ │ - Wraps native fetch/XHR │ │
│ │ - Sends requests to Content Bridge │ │
│ └───────────────┬───────────────────────────────────┘ │
└──────────────────┼────────────────────────────────────────┘
│ postMessage
┌──────────────────▼────────────────────────────────────────┐
│ Content Bridge (ISOLATED world) │
│ - Relays messages between page and background │
└──────────────────┬────────────────────────────────────────┘
│ chrome.runtime.sendMessage
┌──────────────────▼────────────────────────────────────────┐
│ Background Service Worker │
│ - Evaluates rules against requests │
│ - Manages IndexedDB storage │
│ - Returns intercept decisions (mock/modify/pass-through) │
└────────────────────────────────────────────────────────────┘
| Component | File | Purpose |
|---|---|---|
| Background Worker | src/background.ts |
Rule evaluation engine and storage manager |
| Page Interceptor | src/page-interceptor.ts |
Hooks into native fetch/XHR in page context |
| Content Bridge | src/content-bridge.ts |
Message relay between page and extension |
| Popup UI | src/App.tsx |
Main extension interface |
| Rule Engine | src/lib/rule-engine.ts |
Rule matching and decision logic |
| Database | src/lib/db.ts |
IndexedDB operations for rule persistence |
-
Prerequisites
node >= 18.x pnpm >= 8.x
-
Clone and Install
git clone https://github.com/Slogllykop/Echo.git cd Echo pnpm install -
Build the Extension
pnpm build
-
Load in Chrome
- Open
chrome://extensions/ - Enable "Developer mode"
- Click "Load unpacked"
- Select the
distfolder
- Open
Coming soon
- Click the Echo extension icon in your browser toolbar
- Click "New Rule" button
- Configure the rule:
- Name: "Mock User API"
- URL Pattern:
https://api.example.com/user - Match Type: "Contains"
- HTTP Method:
GET - Action: "Mock Response"
- Status Code:
200 - Response Body:
{ "id": 1, "name": "John Doe", "email": "john@example.com" }
- Click "Save Rule"
Toggle the "Extension" switch to ON in the header.
Visit a page that makes requests to https://api.example.com/user. Echo will intercept the request and return your mocked response.
Rules define how Echo handles intercepted requests. Each rule consists of:
- URL Pattern: String to match against request URLs
- Match Type:
contains: URL contains the pattern (case-insensitive)exact: URL exactly matches the patternregex: URL matches the regex pattern
- HTTP Method:
GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS,*(any)
Mock Response - Return a fake response without hitting the API
- Status Code (e.g.,
200,404,500) - Response Headers (optional)
- Response Body (JSON or plain text)
- Delay (milliseconds)
Modify Request/Response - Patch real requests/responses
- Modify Request Headers
- Modify Request Body
- Modify Response Headers
- Modify Response Body
- Modify Status Code
- Body Patch Strategy:
none,replace,merge-json
Rules are evaluated in order from top to bottom. The first matching rule wins.
Match Logic:
- URL pattern matches
- HTTP method matches (or is
*) - Rule is enabled
Reorder Rules: Drag and drop rules using the ↑↓ buttons to change evaluation priority.
| Strategy | Behavior |
|---|---|
none |
Don't modify the body |
replace |
Replace entire body with new value |
merge-json |
Deep merge JSON objects (keeps existing keys) |
Example: Merge JSON
Original Response:
{
"user": { "name": "Alice", "age": 30 },
"posts": []
}Merge Patch:
{
"user": { "age": 31, "city": "NYC" }
}Result:
{
"user": { "name": "Alice", "age": 31, "city": "NYC" },
"posts": []
}Export Rules
- Click "Export" button
- Rules are downloaded as
echo-rules-YYYY-MM-DD.json
Import Rules
- Click "Import" button
- Select a valid Echo JSON file
- Confirm to replace existing rules
Export File Format:
{
"version": "1.0.1",
"exportedAt": "2026-02-20T01:35:33.000Z",
"rules": [
{
"id": "uuid",
"name": "Rule Name",
"enabled": true,
"order": 0,
"match": { ... },
"action": { ... }
}
]
}# Install dependencies
pnpm install
# Start dev server with HMR
pnpm devLoad Extension in Development Mode:
- Open
chrome://extensions/ - Enable "Developer mode"
- Click "Load unpacked"
- Select the project root directory (not
dist) - Keep
pnpm devrunning for hot module replacement
| Command | Description |
|---|---|
pnpm dev |
Start Vite dev server with HMR |
pnpm build |
Production build to dist/ |
pnpm preview |
Preview production build |
pnpm lint |
Run Biome linter |
pnpm lint:fix |
Fix linting issues |
pnpm format |
Format code with Biome |
pnpm release |
Create a new version with standard-version |
- Framework: React 19
- Language: TypeScript 5.9
- Build Tool: Vite 7 with
@crxjs/vite-plugin - Styling: Tailwind CSS 4 + shadcn/ui
- Code Editor: CodeMirror 6
- Linting: Biome
- Storage: IndexedDB (via custom wrapper)
- Versioning: standard-version
# Build optimized extension
pnpm build
# Output: dist/
# ├── manifest.json
# ├── index.html
# ├── assets/
# └── src/ (transpiled background/content scripts)- Open
chrome://extensions/ - Click "Load unpacked"
- Select the
dist/folder
# Create a new version
pnpm release
# Manually zip dist/ folder for Chrome Web Store upload
cd dist
zip -r ../echo-extension.zip .echo/
├── public/
│ └── icons/ # Extension icons (16, 32, 48, 128)
├── src/
│ ├── components/ # React components
│ │ ├── ui/ # shadcn/ui base components
│ │ ├── rule-editor.tsx
│ │ ├── request-simulator.tsx
│ │ └── code-editor.tsx
│ ├── lib/
│ │ ├── types.ts # TypeScript type definitions
│ │ ├── rule-engine.ts # Rule matching logic
│ │ ├── db.ts # IndexedDB operations
│ │ ├── runtime.ts # Chrome runtime messaging
│ │ └── utils.ts # Utility functions
│ ├── App.tsx # Main popup UI
│ ├── main.tsx # React app entry point
│ ├── background.ts # Background service worker
│ ├── page-interceptor.ts # Page-level fetch/XHR hooks
│ ├── content-bridge.ts # Content script bridge
│ └── index.css # Global styles
├── manifest.json # Chrome extension manifest
├── vite.config.ts # Vite configuration
├── tsconfig.json # TypeScript configuration
├── biome.json # Biome linter config
└── package.json
Echo intercepts requests using dual injection:
-
Page Interceptor (MAIN world)
- Runs in the same JavaScript context as the web page
- Wraps
window.fetchandwindow.XMLHttpRequestprototypes - Sends intercepted request details via
postMessage
-
Content Bridge (ISOLATED world)
- Runs in isolated content script context
- Listens for
postMessagefrom page interceptor - Relays messages to background worker via
chrome.runtime.sendMessage
This architecture ensures:
- ✅ All requests are captured (even from inline scripts)
- ✅ Extension code is isolated from page scripts
- ✅ No CORS issues with chrome extension APIs
// Pseudo-code evaluation flow
function evaluateRules(rules: EchoRule[], request: InterceptRequest) {
for (const rule of rules) {
if (!rule.enabled) continue;
if (matchesUrl(rule.match.urlPattern, request.url, rule.match.matchType) &&
matchesMethod(rule.match.method, request.method)) {
if (rule.action.kind === 'mock') {
return { type: 'mock', ...rule.action.mock };
} else {
return { type: 'modify', ...rule.action.modify };
}
}
}
return { type: 'pass-through' };
}IndexedDB Database: echo-rules-db
Object Store: rules
- Key:
rule.id(UUID) - Value:
EchoRuleobject - Index:
order(for sorting)
Chrome Storage: chrome.storage.local
echo:enabled(boolean) - Extension master toggleecho:rules-cache(EchoRule[]) - Cached rules for performance
Cause: Chrome is trying to load source files instead of built files.
Solution:
- Dev Mode: Run
pnpm devand keep it running, load unpacked from root directory - Production Mode: Run
pnpm build, load unpacked from dist/ directory
Cause: Extension not pinned to toolbar.
Solution:
- Click the puzzle piece icon in Chrome toolbar
- Find "Echo"
- Click the pin icon
Checklist:
- ✅ Extension toggle is ON
- ✅ Rule is enabled (checkbox)
- ✅ URL pattern matches the request URL
- ✅ HTTP method matches (or is set to
*) - ✅ Rule order is correct (first match wins)
- ✅ Page is refreshed after creating/editing rules
Debug:
- Open DevTools → Network tab
- Check if requests are being made
- Open Echo popup → Request Simulator
- Paste request URL and test rule matching
Cause: Invalid JSON in response body or body patch.
Solution:
- Use the Format button in the code editor
- Validate JSON using a JSON validator
- Check for trailing commas, missing quotes, etc.
Cause: IndexedDB storage quota exceeded or browser issue.
Solution:
- Open
chrome://settings/content/all - Search for your site
- Clear storage and data
- Export rules before clearing
- Re-import after clearing
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Use TypeScript for all new code
- Follow the existing code style (enforced by Biome)
- Add JSDoc comments for public APIs
- Write meaningful commit messages
# Lint and format
pnpm lint
pnpm format
# Build and verify
pnpm buildMIT License - see LICENSE file for details.
- Built with React
- UI components from shadcn/ui
- Icons from Tabler Icons
- Code editor powered by CodeMirror
Made with ❤️ by the Echo team