Skip to content

Latest commit

 

History

History
1157 lines (966 loc) · 31 KB

File metadata and controls

1157 lines (966 loc) · 31 KB

ApiTap — Chrome Extension API Tester

A full-featured API testing tool, right in your browser. Like Postman, but as a Chrome Extension. 100% local. No server. No account.


1. Project Overview

Field Value
Extension Name GhostAPI
Tagline Test APIs without leaving your browser
Version 1.0.0
License GPL v3
Platform Chrome Extension (Manifest V3)
Also publish to Firefox Add-ons (same codebase)
Data storage 100% local — chrome.storage.local
No server required Zero backend, zero API calls to any third party

What It Does

A full Postman-like API tester as a Chrome Extension. Send HTTP requests (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS), test WebSocket connections, manage collections, view history, set environments/variables, and inspect responses — all without leaving the browser, without an account, and without any server.


2. Extension Architecture

2.1 Extension Type

Full-page app opened in a new tab. NOT a small popup — this is a full workspace like Postman. Click extension icon → opens full page app in new tab.

2.2 Manifest V3 (manifest.json)

{
  "manifest_version": 3,
  "name": "ApiTap",
  "version": "1.0.0",
  "description": "Full-featured API tester. Like Postman, right in your browser.",
  "permissions": [
    "storage",
    "unlimitedStorage",
    "tabs",
    "webRequest",
    "cookies",
    "clipboardWrite",
    "clipboardRead",
    "notifications"
  ],
  "host_permissions": ["<all_urls>"],
  "action": {
    "default_title": "ApiTap",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self'"
  }
}

2.3 Tech Stack

Layer Technology
UI Framework React 18 + Vite
Styling Tailwind CSS v3
Icons Lucide React
Fonts Geist (UI), Geist Mono (code/URLs/JSON)
HTTP Requests Native fetch() API
WebSocket Native WebSocket API
Storage chrome.storage.local (unlimited)
Code editor CodeMirror 6
Build Vite + vite-plugin-web-extension

3. UI Design — Top Priority

3.1 Design Philosophy

The UI must feel like a premium desktop app, not a browser extension. Think Postman + Linear + Raycast. Clean, dense information layout, no wasted space, every pixel intentional. Dark by default.

3.2 Layout — Three Panel Design

+------------------+--------------------------------------------------+
|                  | [GET v] [https://api.example.com/users  ] [Send] |
|  SIDEBAR         +--------------------------------------------------+
|                  | Params | Auth | Headers | Body | Pre-req | Tests |
|  Collections     +--------------------------------------------------+
|  +-MyAPIs        |  [Request config area]                           |
|  |  GET /users   |                                                  |
|  |  POST /login  +--------------------------------------------------+
|  +-AuthTests     | Response          200 OK    124ms    4.2KB       |
|                  | Body | Headers | Cookies | Timeline | Tests      |
|  History         |                                                  |
|  Environments    |  [Response body / JSON tree viewer]              |
|                  |                                                  |
|  Settings        |                                                  |
+------------------+--------------------------------------------------+

3.3 Color System (CSS Variables)

/* Dark Theme (default) */
:root[data-theme="dark"] {
  --bg-primary:     #0d0d0d;
  --bg-secondary:   #141414;
  --bg-tertiary:    #1a1a1a;
  --bg-hover:       #222222;
  --bg-active:      #2a2a2a;
  --border:         #262626;
  --border-strong:  #333333;
  --text-primary:   #ededed;
  --text-secondary: #888888;
  --text-tertiary:  #555555;
  --accent:         #f97316;
  --accent-hover:   #fb923c;
  --accent-muted:   rgba(249,115,22,0.12);
  --green:          #22c55e;
  --yellow:         #eab308;
  --red:            #ef4444;
  --blue:           #3b82f6;
  --purple:         #a855f7;
  --method-get:     #22c55e;
  --method-post:    #f97316;
  --method-put:     #3b82f6;
  --method-patch:   #a855f7;
  --method-delete:  #ef4444;
  --method-head:    #eab308;
  --method-options: #06b6d4;
  --method-ws:      #a855f7;
  --font-ui:        'Geist', system-ui, sans-serif;
  --font-mono:      'Geist Mono', 'Fira Code', monospace;
  --radius-sm:      4px;
  --radius-md:      6px;
  --radius-lg:      10px;
}

/* Light Theme */
:root[data-theme="light"] {
  --bg-primary:     #ffffff;
  --bg-secondary:   #f5f5f5;
  --bg-tertiary:    #ebebeb;
  --bg-hover:       #e0e0e0;
  --bg-active:      #d4d4d4;
  --border:         #e5e5e5;
  --border-strong:  #d4d4d4;
  --text-primary:   #0a0a0a;
  --text-secondary: #525252;
  --text-tertiary:  #a3a3a3;
  --accent:         #ea6c00;
  --accent-hover:   #f97316;
  --accent-muted:   rgba(234,108,0,0.08);
}

3.4 Typography

  • UI text: Geist from @fontsource/geist
  • Code, JSON, URLs, headers: Geist Mono
  • Sizes: 11px (labels), 12px (secondary), 13px (body), 14px (input)
  • Weights: 400, 500, 600

3.5 Sidebar (Left — 240px, resizable)

[+ New Request]

  Search requests...

COLLECTIONS
  ▶ My APIs            [•••]
      GET   User List
      POST  Create User
      PUT   Update User

  ▶ Auth Tests         [•••]
      POST  Login

HISTORY
  GET   /api/users     2m ago
  POST  /auth/login    1h ago

ENVIRONMENTS
  ● Production
  ○ Staging
  ○ Local

──────────────────────
  ⚙ Settings
  🌙 Theme toggle
  • Method badge colored by HTTP method
  • Active item: 3px solid var(--accent) left border + --accent-muted bg
  • Right-click context menu: Rename, Duplicate, Move, Delete
  • Drag to reorder

3.6 Request Bar

[ GET ▼ ]  [ https://api.example.com/{{version}}/users  ] [  Send  ]
  • Method dropdown: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, WebSocket
  • Method color changes on selection
  • URL: monospace font, {{variable}} highlighted orange, unknown vars red
  • Send button: accent color, spinner on loading
  • Ctrl+Enter to send

3.7 Request Tabs

Params (2)  |  Auth  |  Headers (1)  |  Body  |  Pre-request  |  Tests
  • Badge count for filled items
  • Active tab: accent bottom border underline

3.8 Response Bar

Status: [ 200 OK ]   Time: [ 124 ms ]   Size: [ 4.2 KB ]   [Save] [Copy] [Clear]
  • 2xx: green, 3xx: yellow, 4xx/5xx: red
  • Response tabs: Body | Headers | Cookies | Timeline | Test Results

4. Features — Complete List

4.1 HTTP Methods Supported

GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS + WebSocket

All methods support: params, headers, auth, pre-request script, tests.

4.2 Params Tab

  • Key-value table: checkbox, key, value, description, delete
  • "Add Param" button
  • Params auto-appended to URL as querystring
  • Bulk edit: paste raw querystring → auto-parse into rows
  • Supports {{variable}} in key and value

4.3 Auth Tab

Type Fields
None
Bearer Token Token input
Basic Auth Username + Password
API Key Key name, Value, Header or Query Param
OAuth 2.0 Grant type, Token URL, Client ID, Secret, Scope
Digest Auth Username + Password
AWS Signature Access Key, Secret, Region, Service

Auto-generates correct Authorization header.

4.4 Headers Tab

  • Key-value table (same as Params)
  • Common headers autocomplete
  • Preset buttons: JSON, XML, Form (auto-fill Content-Type)
  • Supports {{variable}}

4.5 Body Tab

Sub-tabs:

None — default for GET/HEAD

Form Data — multipart/form-data

  • Key-value table, value type: Text or File

URL Encoded — application/x-www-form-urlencoded

  • Key-value table, auto-encodes

Raw — with format selector:

  • JSON (CodeMirror + JSON syntax + validation)
  • XML (CodeMirror)
  • HTML (CodeMirror)
  • Text (textarea)
  • JavaScript (CodeMirror)

Binary — file picker, raw body

GraphQL

  • Query editor (CodeMirror)
  • Variables editor (JSON, CodeMirror)

4.6 Pre-request Script Tab

CodeMirror JS editor. Runs before request. Available API:

pm.environment.set('token', 'abc123');
pm.globals.set('baseUrl', 'https://api.example.com');
const token = pm.environment.get('token');
pm.request.headers.add({ key: 'X-Custom', value: 'hello' });
console.log('running pre-request');

4.7 Tests Tab

CodeMirror JS editor. Runs after response. Available API:

pm.test('Status is 200', () => {
  pm.expect(pm.response.status).to.equal(200);
});

pm.test('Has user id', () => {
  const body = pm.response.json();
  pm.expect(body).to.have.property('id');
});

pm.environment.set('userId', pm.response.json().id);

pm.test('Fast response', () => {
  pm.expect(pm.response.time).to.be.below(500);
});

Test results shown in response area after send.


4.8 Response Viewer

Body Tab

  • Pretty — formatted syntax-highlighted JSON/XML/HTML (CodeMirror read-only)
  • Raw — raw text monospace
  • Preview — render HTML in iframe
  • JSON Tree — interactive collapsible tree
    • Click expand/collapse nodes
    • Click value → copy to clipboard
    • Search/filter within tree
    • Hover → show full JSON path (e.g. data[0].user.name)
  • Copy all button
  • Download button
  • Search: Ctrl+F

Headers Tab

  • Table: name | value
  • Click value → copy

Cookies Tab

  • Name | Value | Domain | Path | Expires | Secure | HttpOnly

Timeline Tab

Visual bar chart of request timing:

  • DNS Lookup
  • TCP Connection
  • TLS Handshake
  • Time to First Byte (TTFB)
  • Content Download
  • Total time

Test Results Tab

  • List: green tick pass / red cross fail
  • Error message for failed tests
  • Summary: "3/4 tests passed"

4.9 WebSocket Tester

Activated when method = WebSocket.

[ WS ▼ ]  [ ws://localhost:3000/ws              ] [Connect] [Disconnect]

Status: ● Connected to ws://localhost:3000/ws

+----------------------------------------------------+
| Message Log                             [Clear]    |
|                                                    |
| ⚡ INFO  14:32:00  Connected to ws://...            |
| ▲ SENT  14:32:01  {"type":"ping"}                  |
| ▼ RECV  14:32:01  {"type":"pong"}                  |
| ▲ SENT  14:32:05  {"action":"subscribe","ch":"x"}  |
| ▼ RECV  14:32:05  {"status":"ok"}                  |
| ✕ ERR   14:35:00  Connection closed (1001)         |
+----------------------------------------------------+

[ Message input (multiline)                ] [Send]
  Format: [ Text ▼ ]    Auto-ping: [off]    Auto-reconnect: [off]

Features:

  • Connect/disconnect
  • Status: grey=disconnected, green=connected, red=error
  • Message log: sent=blue arrow, received=green arrow, info=grey, error=red
  • Send text or JSON
  • JSON format button for message input
  • Auto-ping to keep alive
  • Auto-reconnect on disconnect
  • Save WS sessions to history
  • Export message log as JSON

4.10 Collections

Structure:

Collection
├── id, name, description, createdAt
├── variables (collection-level)
├── auth (default for all requests)
└── requests[]
    ├── id, name, method, url
    ├── params[], headers[], body
    ├── auth, preRequestScript, tests

Features:

  • Create/rename/delete collections
  • Add current request to collection
  • Rename/duplicate/move/delete requests
  • Drag to reorder within and between collections
  • Run Collection: run all requests in sequence → pass/fail report
  • Export as JSON (Postman v2.1 compatible)
  • Import from JSON

4.11 Environments & Variables

{
  "name": "Production",
  "variables": [
    { "key": "baseUrl", "value": "https://api.myapp.com", "enabled": true },
    { "key": "token",   "value": "secret123",             "secret": true }
  ]
}

Variable usage: {{variableName}} in URL, headers, params, body.

Priority (highest first):

  1. Local vars (from pre-request script)
  2. Environment vars
  3. Global vars

UI:

  • Environment selector dropdown (top right)
  • Environment manager modal: add, edit, delete, duplicate, export, import
  • Secret variables masked by default

4.12 Request History

  • Every request auto-saved
  • Max 500 entries (oldest removed)
  • Shows: method, URL, status, time, timestamp
  • Click → reload into editor
  • Filter by method, status, URL
  • Clear all button
  • Pin important entries

4.13 Code Generation

Generate code from any request:

Language Library
JavaScript fetch
JavaScript axios
JavaScript XMLHttpRequest
Python requests
Python httpx
PHP cURL
PHP Guzzle
cURL command line
C# HttpClient
Go net/http
Ruby Net::HTTP

Modal with language tabs, CodeMirror read-only, one-click copy.

4.14 Import

  • Postman Collection v2.1 JSON
  • Postman Environment JSON
  • cURL command (paste curl → auto-parse)
  • OpenAPI / Swagger JSON or YAML → import as collection

4.15 Export

  • Collection as JSON (Postman-compatible)
  • Environment as JSON
  • Full workspace backup (all data) as JSON

4.16 Command Palette (Ctrl+K)

+----------------------------------------+
| > Search requests, commands...          |
+----------------------------------------+
| RECENT                                  |
|   GET  /api/users                       |
|   POST /auth/login                      |
| COLLECTIONS                             |
|   GET  My APIs / User List              |
| ACTIONS                                 |
|   New Request                           |
|   Import Collection                     |
|   Switch Environment                    |
+----------------------------------------+

4.17 Request Tabs

Like VS Code tabs — multiple requests open simultaneously.

[+ New]  [GET /users ×]  [POST /login ×]  [WS /chat ×]
  • Each tab independent
  • Tabs persist across extension reopen
  • Max 20 tabs
  • Drag to reorder
  • Unsaved indicator: dot on tab name
  • Right-click: Duplicate, Close, Close Others, Close All

4.18 Keyboard Shortcuts

Shortcut Action
Ctrl+Enter Send request
Ctrl+N New tab
Ctrl+W Close tab
Ctrl+S Save to collection
Ctrl+D Duplicate request
Ctrl+L Focus URL bar
Ctrl+E Switch environment
Ctrl+K Command palette
Ctrl+/ Toggle sidebar
Ctrl+Shift+F Search in response
Ctrl+Alt+C Copy response
Ctrl+Alt+G Code generation
Escape Cancel / close modal

5. HTTP Request Execution

All via native fetch(). No library.

// src/utils/request-runner.js

async function runRequest(request, environment, globals, settings) {
  // 1. Resolve {{variables}}
  const resolved = resolveVariables(request, { ...globals, ...environment });

  // 2. Build URL + query params
  const url = new URL(resolved.url);
  resolved.params.filter(p => p.enabled).forEach(p =>
    url.searchParams.append(p.key, p.value)
  );

  // 3. Build headers
  const headers = {};
  resolved.headers.filter(h => h.enabled).forEach(h =>
    headers[h.key] = h.value
  );

  // 4. Apply auth
  applyAuth(resolved.auth, headers);

  // 5. Build body
  const body = buildBody(resolved.body);

  // 6. Run pre-request script
  const pmCtx = createPmContext(environment, globals);
  if (resolved.preRequestScript) {
    await runScript(resolved.preRequestScript, pmCtx);
  }

  // 7. Send
  const start = performance.now();
  const fetchRes = await fetch(url.toString(), {
    method: resolved.method,
    headers,
    body: ['GET','HEAD'].includes(resolved.method) ? undefined : body,
    redirect: settings.followRedirects ? 'follow' : 'manual',
    signal: AbortSignal.timeout(settings.timeout),
  });

  const bodyText = await fetchRes.text();
  const response = {
    status: fetchRes.status,
    statusText: fetchRes.statusText,
    headers: Object.fromEntries(fetchRes.headers.entries()),
    body: bodyText,
    time: Math.round(performance.now() - start),
    size: new Blob([bodyText]).size,
  };

  // 8. Run tests
  pmCtx.response = response;
  const testResults = resolved.tests
    ? await runTests(resolved.tests, response, pmCtx)
    : [];

  return { response, testResults };
}

function resolveVariables(obj, vars) {
  const str = JSON.stringify(obj);
  const resolved = str.replace(/\{\{(\w+)\}\}/g, (_, key) =>
    vars[key] !== undefined ? vars[key] : `{{${key}}}`
  );
  return JSON.parse(resolved);
}

6. WebSocket Implementation

// src/utils/websocket-runner.js

class WebSocketRunner {
  constructor(url, onMessage, onStatus) {
    this.url = url;
    this.onMessage = onMessage;
    this.onStatus = onStatus;
    this.ws = null;
  }

  connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      this.onStatus('connected');
      this.onMessage({ type: 'info', text: `Connected to ${this.url}`, time: new Date() });
    };

    this.ws.onmessage = (e) => {
      this.onMessage({ type: 'received', text: e.data, time: new Date() });
    };

    this.ws.onerror = () => {
      this.onStatus('error');
      this.onMessage({ type: 'error', text: 'Connection error', time: new Date() });
    };

    this.ws.onclose = (e) => {
      this.onStatus('disconnected');
      this.onMessage({ type: 'info', text: `Closed (${e.code})`, time: new Date() });
    };
  }

  send(message) {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(message);
      this.onMessage({ type: 'sent', text: message, time: new Date() });
    }
  }

  disconnect() {
    this.ws?.close();
  }
}

export default WebSocketRunner;

7. cURL Parser

// src/utils/curl-parser.js

export function parseCurl(curlString) {
  const result = { method: 'GET', url: '', headers: [], params: [], body: { type: 'none' } };

  const methodMatch = curlString.match(/-X\s+(\w+)/);
  if (methodMatch) result.method = methodMatch[1].toUpperCase();

  const urlMatch = curlString.match(/curl\s+(?:-[^\s]+\s+(?:[^\s]+\s+)?)*['"]?(https?:\/\/[^\s'"]+)['"]?/);
  if (urlMatch) result.url = urlMatch[1];

  for (const match of curlString.matchAll(/-H\s+['"]([^'"]+)['"]/g)) {
    const [key, ...rest] = match[1].split(':');
    result.headers.push({ key: key.trim(), value: rest.join(':').trim(), enabled: true });
  }

  const dataMatch = curlString.match(/(?:-d|--data|--data-raw)\s+['"]([^'"]+)['"]/);
  if (dataMatch) {
    result.body = { type: 'raw', format: 'json', content: dataMatch[1] };
    if (!methodMatch) result.method = 'POST';
  }

  return result;
}

8. Code Generator

// src/utils/code-generator.js

export const generators = {
  curl: (req) => {
    const lines = [`curl -X ${req.method} '${req.url}'`];
    req.headers.filter(h => h.enabled).forEach(h => lines.push(`  -H '${h.key}: ${h.value}'`));
    if (req.body?.content) lines.push(`  -d '${req.body.content}'`);
    return lines.join(' \\\n');
  },

  fetch: (req) => `const response = await fetch('${req.url}', {
  method: '${req.method}',
  headers: {
${req.headers.filter(h=>h.enabled).map(h=>`    '${h.key}': '${h.value}'`).join(',\n')}
  },${req.body?.content ? `\n  body: JSON.stringify(${req.body.content}),` : ''}
});
const data = await response.json();`,

  axios: (req) => `const { data } = await axios({
  method: '${req.method.toLowerCase()}',
  url: '${req.url}',
  headers: {
${req.headers.filter(h=>h.enabled).map(h=>`    '${h.key}': '${h.value}'`).join(',\n')}
  },${req.body?.content ? `\n  data: ${req.body.content},` : ''}
});`,

  python: (req) => `import requests

r = requests.${req.method.toLowerCase()}(
    '${req.url}',
    headers={
${req.headers.filter(h=>h.enabled).map(h=>`        '${h.key}': '${h.value}'`).join(',\n')}
    },${req.body?.content ? `\n    json=${req.body.content},` : ''}
)
print(r.json())`,

  php: (req) => `$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, '${req.url}');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, '${req.method}');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
${req.headers.filter(h=>h.enabled).map(h=>`  '${h.key}: ${h.value}'`).join(',\n')}
]);${req.body?.content ? `\ncurl_setopt($ch, CURLOPT_POSTFIELDS, '${req.body.content}');` : ''}
$response = curl_exec($ch);
curl_close($ch);`,
};

9. Storage Schema (chrome.storage.local)

{
  "settings": {
    "theme": "dark",           // dark | light | system
    "fontSize": "medium",      // small | medium | large
    "defaultMethod": "GET",
    "sslVerification": true,
    "followRedirects": true,
    "maxRedirects": 5,
    "timeout": 30000,
    "saveHistory": true,
    "historyLimit": 500,
    "autoFormatResponse": true,
    "wordWrap": false,
    "lineNumbers": true
  },

  "collections": [
    {
      "id": "col_abc",
      "name": "My APIs",
      "description": "",
      "createdAt": "2026-04-01T10:00:00Z",
      "variables": [],
      "auth": { "type": "none" },
      "requests": [
        {
          "id": "req_xyz",
          "name": "Get Users",
          "method": "GET",
          "url": "{{baseUrl}}/users",
          "params":  [{ "key": "page",   "value": "1",                "enabled": true }],
          "headers": [{ "key": "Accept", "value": "application/json", "enabled": true }],
          "body": { "type": "none" },
          "auth": { "type": "inherit" },
          "preRequestScript": "",
          "tests": "",
          "createdAt": "2026-04-01T10:00:00Z"
        }
      ]
    }
  ],

  "environments": [
    {
      "id": "env_abc",
      "name": "Production",
      "variables": [
        { "key": "baseUrl", "value": "https://api.myapp.com", "enabled": true, "secret": false },
        { "key": "token",   "value": "secret123",             "enabled": true, "secret": true  }
      ]
    }
  ],

  "activeEnvironment": "env_abc",

  "globals": [
    { "key": "appVersion", "value": "2", "enabled": true }
  ],

  "history": [
    {
      "id": "hist_001",
      "method": "GET",
      "url": "https://api.example.com/users",
      "status": 200,
      "time": 124,
      "size": 4200,
      "timestamp": "2026-04-04T14:32:01Z",
      "pinned": false,
      "request":  { },
      "response": { "status": 200, "headers": {}, "body": "...", "time": 124 }
    }
  ],

  "tabs": [
    {
      "id": "tab_001",
      "name": "New Request",
      "isDirty": false,
      "request": { "method": "GET", "url": "", "params": [], "headers": [], "body": { "type": "none" } }
    }
  ],

  "activeTabId": "tab_001"
}

10. Storage Helper (src/utils/storage.js)

const storage = {
  get: (key) => new Promise(resolve =>
    chrome.storage.local.get(key, r => resolve(r[key]))
  ),
  set: (key, value) => new Promise(resolve =>
    chrome.storage.local.set({ [key]: value }, resolve)
  ),
  remove: (key) => new Promise(resolve =>
    chrome.storage.local.remove(key, resolve)
  ),
  getAll: () => new Promise(resolve =>
    chrome.storage.local.get(null, resolve)
  ),
};

export default storage;

11. Project File Structure

apitap/
├── manifest.json
├── package.json
├── vite.config.js
├── tailwind.config.js
├── index.html
│
├── public/
│   └── icons/
│       ├── icon16.png
│       ├── icon48.png
│       └── icon128.png
│
├── src/
│   ├── main.jsx
│   ├── App.jsx
│   ├── index.css
│   ├── background.js
│   │
│   ├── components/
│   │   ├── layout/
│   │   │   ├── Sidebar.jsx
│   │   │   ├── TabBar.jsx
│   │   │   ├── RequestPanel.jsx
│   │   │   └── ResponsePanel.jsx
│   │   │
│   │   ├── request/
│   │   │   ├── RequestBar.jsx
│   │   │   ├── ParamsTab.jsx
│   │   │   ├── AuthTab.jsx
│   │   │   ├── HeadersTab.jsx
│   │   │   ├── BodyTab.jsx
│   │   │   ├── PreRequestTab.jsx
│   │   │   └── TestsTab.jsx
│   │   │
│   │   ├── response/
│   │   │   ├── ResponseBar.jsx
│   │   │   ├── BodyViewer.jsx
│   │   │   ├── JsonTree.jsx
│   │   │   ├── HeadersViewer.jsx
│   │   │   ├── CookiesViewer.jsx
│   │   │   ├── TimelineViewer.jsx
│   │   │   └── TestResults.jsx
│   │   │
│   │   ├── websocket/
│   │   │   ├── WsBar.jsx
│   │   │   ├── WsMessageLog.jsx
│   │   │   └── WsMessageInput.jsx
│   │   │
│   │   ├── sidebar/
│   │   │   ├── Collections.jsx
│   │   │   ├── CollectionItem.jsx
│   │   │   ├── RequestItem.jsx
│   │   │   ├── HistoryPanel.jsx
│   │   │   └── EnvironmentsPanel.jsx
│   │   │
│   │   ├── modals/
│   │   │   ├── CommandPalette.jsx
│   │   │   ├── CodeGenModal.jsx
│   │   │   ├── EnvManager.jsx
│   │   │   ├── ImportModal.jsx
│   │   │   ├── CollectionRunner.jsx
│   │   │   └── SettingsModal.jsx
│   │   │
│   │   └── ui/
│   │       ├── Button.jsx
│   │       ├── Input.jsx
│   │       ├── Select.jsx
│   │       ├── Badge.jsx
│   │       ├── Tabs.jsx
│   │       ├── KeyValueTable.jsx
│   │       ├── CodeEditor.jsx
│   │       ├── Toast.jsx
│   │       ├── Tooltip.jsx
│   │       ├── Dropdown.jsx
│   │       └── Modal.jsx
│   │
│   ├── hooks/
│   │   ├── useRequest.js
│   │   ├── useTabs.js
│   │   ├── useCollections.js
│   │   ├── useHistory.js
│   │   ├── useEnvironments.js
│   │   ├── useSettings.js
│   │   ├── useWebSocket.js
│   │   ├── useTheme.js
│   │   └── useKeyboard.js
│   │
│   └── utils/
│       ├── request-runner.js
│       ├── websocket-runner.js
│       ├── variable-resolver.js
│       ├── script-runner.js
│       ├── curl-parser.js
│       ├── openapi-importer.js
│       ├── code-generator.js
│       ├── storage.js
│       ├── export.js
│       ├── import.js
│       └── id-generator.js
│
└── README.md

12. package.json

{
  "name": "apitap",
  "version": "1.0.0",
  "description": "Full-featured API tester Chrome Extension",
  "scripts": {
    "dev": "vite build --watch",
    "build": "vite build",
    "zip": "npm run build && node scripts/zip.js"
  },
  "dependencies": {
    "@codemirror/lang-json":       "^6.0.0",
    "@codemirror/lang-xml":        "^6.0.0",
    "@codemirror/lang-html":       "^6.0.0",
    "@codemirror/lang-javascript": "^6.0.0",
    "@fontsource/geist":           "^5.0.0",
    "@fontsource/geist-mono":      "^5.0.0",
    "@uiw/react-codemirror":       "^4.23.0",
    "codemirror":                  "^6.0.0",
    "lucide-react":                "^0.469.0",
    "react":                       "^18.3.0",
    "react-dom":                   "^18.3.0"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "^4.3.0",
    "autoprefixer":         "^10.4.0",
    "postcss":              "^8.4.0",
    "tailwindcss":          "^3.4.0",
    "vite":                 "^6.0.0"
  }
}

13. vite.config.js

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';

export default defineConfig({
  plugins: [react()],
  build: {
    outDir: 'dist',
    emptyOutDir: true,
    rollupOptions: {
      input: {
        main:       resolve(__dirname, 'index.html'),
        background: resolve(__dirname, 'src/background.js'),
      },
      output: {
        entryFileNames: '[name].js',
        chunkFileNames: 'chunks/[name].js',
        assetFileNames: 'assets/[name][extname]',
      },
    },
  },
});

14. How to Load Extension in Chrome During Development

npm install
npm run dev          # builds and watches for changes

# In Chrome:
# 1. Go to: chrome://extensions
# 2. Enable "Developer mode" (top right toggle)
# 3. Click "Load unpacked"
# 4. Select the /dist folder
# 5. Click extension icon → opens in new tab

# After code changes:
# Vite rebuilds automatically
# Go to chrome://extensions → click refresh icon on ApiTap card
# Reload the extension tab

15. Chrome Web Store Submission Checklist

  • Category: Developer Tools
  • Privacy policy page required (host on GitHub Pages)
  • Privacy policy must state: "ApiTap stores all data locally. No data is sent to any server. No account required. No tracking."
  • Screenshots: minimum 3, size 1280x800
  • Promotional tile: 440x280
  • Description: mention "no account, no server, 100% private, works offline"
  • Single purpose declaration: "API testing and debugging"

16. Build Phases (Give AI Agent One Phase at a Time)

Phase 1 — Core Layout + Basic HTTP

  • Full 3-panel layout (sidebar + tab bar + request panel + response panel)
  • Custom dark/light theme with CSS variables
  • Method dropdown + URL bar + Send button
  • Params tab + Headers tab
  • Send GET/POST/PUT/DELETE/PATCH/DELETE via fetch()
  • Response viewer: Pretty JSON + Raw text
  • Status code + time + size in response bar
  • Basic tab system (new, close, switch)
  • chrome.storage.local persistence for tabs

Phase 2 — Auth + Body + Response

  • Auth tab: Bearer, Basic, API Key
  • Body tab: None, Raw JSON/XML/Text, Form Data, URL Encoded
  • CodeMirror editor for body (JSON syntax + validation)
  • Response: Headers viewer, copy body, download body
  • JSON Tree viewer (interactive collapsible)
  • Response search (Ctrl+F)

Phase 3 — Collections + History

  • Collections: create, add request, rename, delete, save, load
  • History: auto-save every request, load from history, clear
  • Drag to reorder in sidebar
  • Export collection as JSON
  • Import Postman collection JSON
  • Full persistence via chrome.storage.local

Phase 4 — Environments + Variables

  • Environment manager modal
  • Variable resolution {{variable}} in URL, headers, params, body
  • Orange highlight for known vars, red for unknown
  • Active environment switcher dropdown
  • Global variables
  • Hover preview of variable value

Phase 5 — Power Features

  • Pre-request script (CodeMirror JS + pm API)
  • Tests tab (CodeMirror JS + pm API + test results)
  • WebSocket tester (connect, send, receive, log)
  • cURL import parser
  • Code generation modal (curl, fetch, axios, python, php)
  • Command palette (Ctrl+K)
  • All keyboard shortcuts
  • GraphQL body support

Phase 6 — Polish + Publish

  • OpenAPI/Swagger import
  • Collection runner (run all in sequence + report)
  • Response timeline visualization
  • Cookies viewer
  • Binary body type
  • All animations and transitions
  • Icons all sizes
  • Chrome Web Store assets (screenshots, tile)
  • Firefox compatibility
  • README with screenshots

End of Requirements. Always give one phase at a time to your AI agent. Test each phase fully before proceeding to the next. Chrome tip: after every rebuild, click refresh on chrome://extensions page.