api-tracer-kit 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,192 @@
1
+ # Frameworks
2
+
3
+ The tracer only needs one thing: `init()` called once, at the entry point,
4
+ before anything makes a request. No bundler, Babel, TypeScript or ESLint
5
+ configuration is required anywhere.
6
+
7
+ ## Plain JavaScript
8
+
9
+ ```html
10
+ <script type="module">
11
+ import { apiTracer } from 'https://esm.sh/api-tracer-kit';
12
+ apiTracer.init();
13
+ </script>
14
+ ```
15
+
16
+ or in a bundled project:
17
+
18
+ ```js
19
+ import { apiTracer } from 'api-tracer-kit';
20
+ apiTracer.init();
21
+ ```
22
+
23
+ ## React
24
+
25
+ ```tsx
26
+ // src/main.tsx — before ReactDOM.render / createRoot
27
+ import { apiTracer } from 'api-tracer-kit';
28
+
29
+ apiTracer.init({ reportTo: import.meta.env.DEV });
30
+ ```
31
+
32
+ Or from inside the tree, which is StrictMode-safe:
33
+
34
+ ```tsx
35
+ import { useApiTracer } from 'api-tracer-kit/react';
36
+
37
+ function App() {
38
+ useApiTracer({ reportTo: true });
39
+ return <Routes />;
40
+ }
41
+ ```
42
+
43
+ `useApiTracer` deliberately does **not** destroy on unmount: in StrictMode that
44
+ would tear the interceptors down immediately after installing them.
45
+
46
+ To render the traces:
47
+
48
+ ```tsx
49
+ import { useApiTraces } from 'api-tracer-kit/react';
50
+
51
+ function CallCount() {
52
+ const { traces, clear } = useApiTraces();
53
+ return <button onClick={clear}>{traces.length} calls</button>;
54
+ }
55
+ ```
56
+
57
+ Updates are batched on an animation frame, so a burst of calls on page load
58
+ re-renders once rather than once per request.
59
+
60
+ ## Create React App
61
+
62
+ CRA's `ModuleScopePlugin` allows imports only from `src/` and `node_modules/`.
63
+ Always import by **package name** — never by a relative path to a checkout
64
+ outside the project:
65
+
66
+ ```js
67
+ import { apiTracer } from 'api-tracer-kit'; // ✅
68
+ import { apiTracer } from '../../../packages/api-tracer-kit'; // ❌ rejected
69
+ ```
70
+
71
+ A `yarn add file:` dependency creates a **symlink**, and webpack resolves
72
+ symlinks to their real path — which lands outside the project and is rejected
73
+ the same way. Depend on a published version, or on a packed tarball:
74
+
75
+ ```bash
76
+ npm pack --pack-destination ./vendor # in the package
77
+ ```
78
+
79
+ ```json
80
+ "api-tracer-kit": "file:vendor/api-tracer-kit-1.0.1.tgz"
81
+ ```
82
+
83
+ Gate it on `REACT_APP_ENV`, not `NODE_ENV` — a dev or stage deploy is a
84
+ production build, so `NODE_ENV` would compile the tracer out of exactly the
85
+ environments it is meant for:
86
+
87
+ ```js
88
+ // src/services/apiRecorder.js
89
+ import axios from 'axios';
90
+ import { apiTracer } from 'api-tracer-kit';
91
+
92
+ const isEnabled =
93
+ process.env.REACT_APP_ENV === 'dev' || process.env.REACT_APP_ENV === 'stage';
94
+
95
+ if (isEnabled) {
96
+ apiTracer.init({ reportTo: process.env.REACT_APP_API_CONSOLE_URL || 'http://localhost:4400' });
97
+ apiTracer.useAxios(axios);
98
+ }
99
+ ```
100
+
101
+ ```js
102
+ // src/index.js — one import, at the top
103
+ import './services/apiRecorder';
104
+ ```
105
+
106
+ Compare `process.env.REACT_APP_ENV` **directly**, not through a variable, so
107
+ DefinePlugin substitutes the literal and the whole condition folds away in a
108
+ production build.
109
+
110
+ ## Vite
111
+
112
+ ```ts
113
+ // src/main.ts
114
+ import { apiTracer } from 'api-tracer-kit';
115
+
116
+ if (import.meta.env.DEV) {
117
+ apiTracer.init({ reportTo: true });
118
+ }
119
+ ```
120
+
121
+ `import.meta.env.DEV` is statically replaced, so the branch is removed from a
122
+ production build.
123
+
124
+ ## Next.js
125
+
126
+ Importing the package on the server is safe — it evaluates no browser global —
127
+ but there is no point tracing a render, so guard the call:
128
+
129
+ ```tsx
130
+ // app/providers.tsx
131
+ 'use client';
132
+
133
+ import { useApiTracer } from 'api-tracer-kit/react';
134
+
135
+ export function Providers({ children }) {
136
+ useApiTracer({ reportTo: process.env.NODE_ENV === 'development' });
137
+ return children;
138
+ }
139
+ ```
140
+
141
+ For the Pages Router, `pages/_app.tsx` works the same way.
142
+
143
+ To trace server-side calls too, `init()` in `instrumentation.ts` — Node has
144
+ `fetch`, so it works there as well:
145
+
146
+ ```ts
147
+ export async function register() {
148
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
149
+ const { apiTracer } = await import('api-tracer-kit');
150
+ apiTracer.init({ include: ['/api/'] });
151
+ }
152
+ }
153
+ ```
154
+
155
+ Server traces stay in that process's memory; they do not reach the browser
156
+ panel.
157
+
158
+ ## Node.js
159
+
160
+ ```js
161
+ import { apiTracer } from 'api-tracer-kit';
162
+
163
+ apiTracer.init();
164
+ await fetch('https://api.example.com/users');
165
+ console.log(apiTracer.getTraces());
166
+ ```
167
+
168
+ Node 18+ has global `fetch`, so it is traced. There is no `XMLHttpRequest`, and
169
+ that transport is skipped rather than erroring. For axios on Node's `http`
170
+ adapter, `useAxios()` is required — no global patch can reach it.
171
+
172
+ ## Testing
173
+
174
+ Use an isolated instance rather than the shared one, so cases cannot leak into
175
+ each other:
176
+
177
+ ```ts
178
+ import { ApiTracer } from 'api-tracer-kit';
179
+
180
+ let tracer;
181
+ beforeEach(() => { tracer = new ApiTracer().init({ transports: ['fetch'] }); });
182
+ afterEach(() => { tracer.destroy(); });
183
+ ```
184
+
185
+ `destroy()` restores the original `fetch`, so a suite that traces is
186
+ indistinguishable from one that does not.
187
+
188
+ ## Webpack, Rollup, esbuild, Parcel
189
+
190
+ Nothing to configure. The package ships ESM and CJS with `sideEffects: false`,
191
+ so an unused import is dropped entirely and the four entry points are bundled
192
+ separately — importing the tracer never pulls in React.
@@ -0,0 +1,120 @@
1
+ # Getting started
2
+
3
+ ## Install
4
+
5
+ ```bash
6
+ npm install api-tracer-kit
7
+ ```
8
+
9
+ Nothing runs on install, and importing the package evaluates no browser global.
10
+ Tracing begins only when you call `init()`.
11
+
12
+ ## One command
13
+
14
+ ```bash
15
+ npx api-tracer setup
16
+ ```
17
+
18
+ It detects your framework, writes a module that configures the tracer, adds the
19
+ import to your entry file, and gitignores the console's data directory. Re-running
20
+ it is safe, and it warns if anything else already calls `init()`.
21
+
22
+ | Option | |
23
+ | --- | --- |
24
+ | `--framework cra\|vite\|next\|node` | override the detection |
25
+ | `--entry <file>` | the entry file to import from |
26
+ | `--out <file>` | where to write the module (default `src/apiTracer.js`) |
27
+ | `--axios false` | do not attach axios |
28
+ | `--envelope false` | your API reports failure in the status line, not the body |
29
+ | `--force` | overwrite an existing file |
30
+
31
+ What it works out for you:
32
+
33
+ - **the framework**, from your dependencies
34
+ - **the environment gate** that your bundler will actually fold — `REACT_APP_ENV`
35
+ for CRA, `import.meta.env.DEV` for Vite, `NODE_ENV` for Next and Node
36
+ - **whether to attach axios**, from whether you depend on it
37
+ - **TypeScript or JavaScript**, from whether you have a `tsconfig.json`
38
+
39
+ ## Or do it by hand
40
+
41
+ Put this at your application's entry point, before anything makes a request:
42
+
43
+ ```ts
44
+ import { apiTracer } from 'api-tracer-kit';
45
+
46
+ apiTracer.init();
47
+ ```
48
+
49
+ Every `fetch` and `XMLHttpRequest` afterwards is traced. Read them back with:
50
+
51
+ ```ts
52
+ apiTracer.getTraces();
53
+ ```
54
+
55
+ ### If you use axios
56
+
57
+ axios instances are plain objects with no global to patch, so hand yours over:
58
+
59
+ ```ts
60
+ import axios from 'axios';
61
+
62
+ apiTracer.init();
63
+ apiTracer.useAxios(axios);
64
+ ```
65
+
66
+ One call covers the default export **and every instance `axios.create()` makes
67
+ afterwards**. See [axios](./tracer.md#axios) for why this is the one integration
68
+ the package cannot do for you.
69
+
70
+ > **Configure it in exactly one place.** `init()` is idempotent — a second call
71
+ > keeps the first one's configuration and warns about the options it ignored.
72
+ > Two modules each calling `init()` is the most common way to end up with a
73
+ > tracer that runs but does nothing you asked for.
74
+
75
+ ## See them
76
+
77
+ Three options, in increasing order of usefulness.
78
+
79
+ **1. The console log.** Fine for a quick check:
80
+
81
+ ```ts
82
+ apiTracer.subscribe((trace) => {
83
+ console.log(trace.request.method, trace.request.url, trace.response?.status, `${trace.timing.duration}ms`);
84
+ });
85
+ ```
86
+
87
+ **2. The in-app panel.** A floating list with search, filters and per-call
88
+ detail. React only:
89
+
90
+ ```tsx
91
+ import { ApiTracerPanel } from 'api-tracer-kit/ui';
92
+
93
+ <ApiTracerPanel enabled={process.env.NODE_ENV !== 'production'} />;
94
+ ```
95
+
96
+ **3. The console.** A separate process that maps every endpoint in your source,
97
+ receives your app's traffic, and lets you re-send any call. This is the one
98
+ worth setting up. See [the console](./console.md).
99
+
100
+ ```bash
101
+ npx api-tracer start
102
+ ```
103
+
104
+ then point the tracer at it:
105
+
106
+ ```ts
107
+ apiTracer.init({ reportTo: true }); // true means http://localhost:4400
108
+ ```
109
+
110
+ ## Where to go next
111
+
112
+ | | |
113
+ | --- | --- |
114
+ | [Tracer API](./tracer.md) | every option, the `ApiTrace` model, storage, lifecycle |
115
+ | [The console](./console.md) | CLI, dashboard, replay, drift, coverage, import/export |
116
+ | [Configuration](./configuration.md) | the config file, scanner presets, sign-in flows |
117
+ | [Frameworks](./frameworks.md) | React, Next.js, Vite, CRA, Node, plain JavaScript |
118
+ | [Troubleshooting](./troubleshooting.md) | when nothing is being recorded |
119
+ | [Security](./security.md) | redaction, what is stored, what is safe to share |
120
+ | [Architecture](./architecture.md) | how it works inside |
@@ -0,0 +1,143 @@
1
+ # Security
2
+
3
+ The tracer sees everything your API sees. On some products that means
4
+ credentials, personal data, and — in health, finance or HR software — data whose
5
+ mishandling is a regulatory matter, not just an embarrassment. Read this before
6
+ deploying a console anywhere but your own laptop.
7
+
8
+ ## What is redacted, by default
9
+
10
+ **Headers**, matched case-insensitively:
11
+
12
+ ```
13
+ authorization, cookie, set-cookie, proxy-authorization, x-api-key, api-key,
14
+ auth_token, access_token, secret_token, x-auth-token, x-csrf-token
15
+ ```
16
+
17
+ **Body and query fields**, matched by name:
18
+
19
+ ```
20
+ /(token|password|passwd|secret|api[-_]?key|authorization|credential|otp|ssn)/i
21
+ ```
22
+
23
+ The **value** is replaced with `<redacted>` and the surrounding shape is kept, so
24
+ the sample still shows what the endpoint expects.
25
+
26
+ Redaction happens **before** a trace is stored, so a real credential is never
27
+ held in the tracer's memory, never reaches a subscriber, and never leaves the
28
+ browser. The console redacts again before writing anything to disk.
29
+
30
+ Extend rather than replace:
31
+
32
+ ```ts
33
+ import { DEFAULT_REDACT_HEADERS } from 'api-tracer-kit';
34
+
35
+ apiTracer.init({
36
+ redactHeaders: [...DEFAULT_REDACT_HEADERS, 'x-internal-key'],
37
+ redactFields: /(token|password|secret|nhs_number|date_of_birth)/i,
38
+ });
39
+ ```
40
+
41
+ ## What is *not* redacted
42
+
43
+ This is the part that matters.
44
+
45
+ Redaction is **name-based**. A field called `notes` containing a patient's
46
+ history, a `body` containing an address, a response listing every user — none of
47
+ those look like credentials, and none are touched. The tracer records response
48
+ bodies in full, up to `maxBodyBytes`.
49
+
50
+ So: **assume every trace and every capture contains real data from the
51
+ environment you are watching.**
52
+
53
+ If that is unacceptable for your product, either narrow what is traced:
54
+
55
+ ```ts
56
+ apiTracer.init({ include: ['/api/reference/'] });
57
+ ```
58
+
59
+ or drop response bodies entirely:
60
+
61
+ ```ts
62
+ apiTracer.init({ maxBodyBytes: 0 });
63
+ ```
64
+
65
+ or post-process in a subscriber before anything is stored or reported.
66
+
67
+ ## Where data ends up
68
+
69
+ ### In the browser
70
+
71
+ Traces live in memory, in a capped ring (500 by default), and disappear on
72
+ reload. Nothing is persisted unless you supply storage that does.
73
+
74
+ ### In the console's data directory
75
+
76
+ `<project>/.api-tracer/` holds captured request and response bodies, response
77
+ shapes, and a real session token.
78
+
79
+ **Add it to `.gitignore` before you run anything:**
80
+
81
+ ```
82
+ .api-tracer/
83
+ ```
84
+
85
+ `tokens.json` is written owner-only (`chmod 600`), but it is still a real
86
+ credential on disk. **Sign out** deletes it, and on a shared machine that is
87
+ worth doing.
88
+
89
+ If it has already been committed, unstage it — the files stay on disk:
90
+
91
+ ```bash
92
+ git rm -r --cached .api-tracer
93
+ ```
94
+
95
+ If it reached a pushed commit, treat the token as compromised and rotate it. The
96
+ bodies are in the history and removing them means rewriting it.
97
+
98
+ ## What is safe to share
99
+
100
+ HAR, cURL and Postman exports carry `<paste your token>` in place of a real
101
+ token, so an export is safe to attach to a ticket or hand to a backend
102
+ developer — **as far as credentials go**.
103
+
104
+ The bodies in them are still real. An export of a busy session is a data
105
+ extract. Treat it accordingly.
106
+
107
+ ## Deploying a console
108
+
109
+ It is a Node process that writes JSON files and holds a token. It is not a
110
+ static asset.
111
+
112
+ - **Keep it off the public internet.** VPN, SSO, or an allowlisted origin behind
113
+ your CDN.
114
+ - **`CONSOLE_USER` / `CONSOLE_PASS`** turn on basic auth — but **`/api/record`
115
+ stays open**, because the app posts recordings from a browser that cannot send
116
+ those credentials. The gate protects reading captures, replaying calls and the
117
+ stored token; it does not stop someone who can reach the host from posting
118
+ junk recordings. If that matters, put the whole thing behind VPN or SSO.
119
+ - **`ALLOWED_ORIGINS`** is an allowlist for posting recordings. Localhost always
120
+ works; every other origin must be named, so a stray site cannot feed or read
121
+ the console.
122
+ - **`HOST`** stays on `127.0.0.1` unless you change it, so a laptop instance is
123
+ not exposed by accident.
124
+ - **Keep the data volume off shared storage.**
125
+ - **Mixed content**: an HTTPS app cannot post to an `http://` console, and the
126
+ browser blocks it silently. Terminate TLS in front of it, or serve it under a
127
+ path on the app's own origin.
128
+
129
+ ## Replaying against production
130
+
131
+ The console refuses to replay writes against an environment named `prod`.
132
+
133
+ That is a guard, not a guarantee — it matches on the environment name you
134
+ configured. Consider commenting out the production base URL in your config
135
+ entirely, so it cannot be selected. `LOCK_ENV` pins a shared console to one
136
+ environment so nobody can switch it.
137
+
138
+ Replay is not a dry run. Every POST creates a real record each time.
139
+
140
+ ## Reporting a vulnerability
141
+
142
+ Open an issue for anything non-sensitive. For a genuine vulnerability, contact
143
+ the maintainer directly rather than filing publicly.