# knock

[한국어 문서](README.ko.md)

**knock** is a Chrome DevTools extension (Manifest V3) for frontend debugging. It patches `window.fetch` in the page (and mirrors `XMLHttpRequest` for many flows), intercepts matching traffic, and lets you **record requests**, **define rules**, **modify the outgoing request**, **synthesize a response**, or **pause** either side and finish it from an in-page floating editor.

## Features

- **Requests tab** — live list of intercepted calls with method, URL, status, timing, and a detail pane (headers, body preview, rule/action context where applicable).
- **Rules tab** — ordered rules with URL + method matching and two independent action sides: a **Request** action and a **Response** action, each `none` / `modify` / `pause`.
- **Scenarios** — save and load the active rule set to `chrome.storage` (named presets in the panel).
- **Export / import** — share rule sets as JSON: **Export** copies or downloads the current rules; **Import** takes a paste or a file and either appends to or replaces your rules (imported rules get fresh ids, so they never collide).
- **On / off switch** — a master toggle (in the panel header) pauses _all_ interception: the content scripts detach from `window.fetch` / `XMLHttpRequest` entirely — nothing is captured or overridden — and reattach when you switch it back on. The state is global and persists.
- **Pass-through fields (fetch)** — for a **Response → modify** action, pick whether status, headers, body, and delay come from your editor, from the real network response, or are merged (headers/body can **overlay** server values on fetch).

### URL matching

Each rule can constrain the URL (first match wins):

| Mode         | Behavior                                                                                                                                              |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **any**      | No URL filter (matches every request if nothing else excludes it).                                                                                    |
| **exact**    | Full string equality.                                                                                                                                 |
| **prefix**   | Request URL starts with the pattern.                                                                                                                  |
| **contains** | `url.includes(pattern)` — fragment appears anywhere. Easiest for `/api/foo` or `opcode=…` style fragments without learning glob/regex.                |
| **glob**     | `*` does not cross `/`; `**` crosses path segments; other characters are literal. Anchored (whole URL).                                               |
| **regex**    | JavaScript `RegExp` source, tested with `.test()` — **unanchored**, matches anywhere. Add `^` / `$` yourself to anchor. Invalid patterns don't match. |

### HTTP method matching

Each rule can also constrain by HTTP method. The panel renders a row of toggle chips:

- **Any** — no method constraint (matches every method, including non-standard verbs).
- **GET / POST / PUT / PATCH / DELETE / HEAD / OPTIONS** — click to include. Multiple selections OR together.

When no method chip is active, the rule matches any method. URL and method criteria are ANDed: both must pass for the rule to fire.

### Request & Response actions

Every rule has **two independent action sides**. Each is `none`, `modify`, or `pause`, and they compose — e.g. _modify the request_ **and** _pause the response_.

#### Request action

- **none** — the outgoing request goes through untouched.
- **modify** — rewrite the outgoing request before it hits the network:
  - **Headers** — key/value rows; each replaces (case-insensitive) any same-named header on the original request, otherwise it is added.
  - **Query params** — key/value rows; each replaces (`URLSearchParams.set`) any same-named search param on the URL, otherwise it is appended.
  - **Body** — none / plain text / JSON / HTML / form / base64. When set, the original body is replaced entirely.
- **pause** — hold the outgoing request in the floating in-page editor; edit headers, query params, and body, then **Accept modified** (or **Accept original** to send it unchanged). The real response of the sent request comes back to the page.

#### Response action

- **none** — the response comes back untouched (after any Request action).
- **modify** — synthesize the response:
  - **HTTP status** and optional **status text**
  - **Headers** — key/value list; with network pass-through on fetch, server headers apply first, then your rows override or add.
  - **Body** — none, plain text, JSON (with basic validation), HTML, form, or raw bytes (base64).
  - **Delay** — optional artificial delay before the promise resolves (skippable via pass-through for fetch).
  - If **no** pass-through flags are set for fetch, the browser never performs the original request for that call.
- **pause** — hold the response in the floating editor; craft status / headers / body, then **Accept modified** (or **Accept original** to keep the real network response).

The floating editor is **draggable**, **resizable**, and uses **CodeMirror** for body editing. Its heading reads **REQUEST** or **RESPONSE** depending on which side is paused. **Cancel** rejects the call (e.g. a failed `fetch`).

### fetch vs XMLHttpRequest

- **`fetch`** — full intercept, synthetic `Response`, optional real network round-trip only when you use pass-through or “Accept original” after pause.
- **`XMLHttpRequest`** — patched so knock can modify, synthesize, or pause; for request modifications and for **Response → modify** with network-backed pieces, knock replays the call via a **mirrored fetch** to read the server response and merge status/headers/body into the XHR surface. Treat XHR as best-effort parity with fetch, not identical in every edge case.

### Frames and injection

Content scripts run in **all frames** at **document start** in the **MAIN** world (plus an isolated bridge). Rules and scenarios sync across the extension so behavior is consistent while you work across iframes.

## Install (developer / unpacked)

**Requirements:** Node.js 20+, pnpm 9+, Chrome 120+.

```bash
pnpm install
pnpm build
```

In Chrome: `chrome://extensions` → **Developer mode** → **Load unpacked** → choose the repository’s **`knock/dist`** directory (the folder that contains `manifest.json` after build).

Open DevTools on any tab; you should see a **knock** panel alongside Console, Network, etc.

### Development workflow

```bash
pnpm dev           # Vite watch → rebuilds panel/devtools/background entries into dist/
pnpm dev:scripts   # (optional, second terminal) watch-build bundled content scripts
```

Reload the extension in `chrome://extensions` after chunks change. For content scripts, a full tab refresh is usually needed.

```bash
pnpm typecheck
pnpm lint
pnpm format:check
pnpm build
pnpm zip
```

`pnpm build` runs a clean Vite production build, post-processes content scripts, and verifies expected artifacts exist.

`pnpm zip` builds, then packages `dist/` into `knock-<version>.zip` with `manifest.json` at the archive root (source maps excluded) — upload that to the Chrome Web Store Developer Dashboard. The store signs the package itself; you do **not** pack a `.crx` (that button in `chrome://extensions` is for self-hosting / enterprise only).

## Tech stack

- **Manifest V3** — `devtools_page`, service worker background, MAIN-world + isolated content scripts.
- **React 18** — DevTools panel UI and scenario management.
- **Vite** — multi-entry build for devtools, panel, background, and content bundles.
- **TypeScript** (strict) — **ESLint 9** + **Prettier**; **Feature-Sliced Design** with **eslint-plugin-boundaries** enforcing import direction.

## Project layout (FSD)

```text
src/
├── app/          Extension entry points (devtools, panel, content, background)
├── pages/        Full pages (panel shell)
├── widgets/      Request list, detail, rule editor, floating pause UI
├── features/     Intercept, match rules, override, pause, scenarios
├── entities/     Request, response, filter rule, scenario models
└── shared/       Messaging, chrome helpers, types, small UI primitives
```

Alias imports: `@app/*`, `@pages/*`, `@widgets/*`, `@features/*`, `@entities/*`, `@shared/*`.

## License

TBD
