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.
- package/CHANGELOG.md +9 -0
- package/README.md +77 -342
- package/cli/bin/api-tracer.mjs +93 -2
- package/cli/setup.mjs +182 -0
- package/cli/test.mjs +97 -0
- package/docs/architecture.md +136 -0
- package/docs/configuration.md +275 -0
- package/docs/console.md +428 -0
- package/docs/frameworks.md +192 -0
- package/docs/getting-started.md +120 -0
- package/docs/security.md +143 -0
- package/docs/tracer.md +330 -0
- package/docs/troubleshooting.md +194 -0
- package/package.json +2 -1
package/docs/tracer.md
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# The tracer
|
|
2
|
+
|
|
3
|
+
The framework-free half of the package. No React, no axios, no DOM required.
|
|
4
|
+
|
|
5
|
+
## `apiTracer`
|
|
6
|
+
|
|
7
|
+
A single shared instance. It is held in a global registry keyed by
|
|
8
|
+
`Symbol.for('api-tracer-kit.registry')`, so `api-tracer-kit` and
|
|
9
|
+
`api-tracer-kit/axios` — separate bundles with separate module graphs — resolve
|
|
10
|
+
to the same object. Without that, `useAxios()` would attach to an instance
|
|
11
|
+
`getTraces()` never reads from.
|
|
12
|
+
|
|
13
|
+
| Method | Description |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `init(options?)` | Installs the interceptors. Idempotent: a second call keeps the first one's configuration and warns about the options it ignored. Returns `this`. |
|
|
16
|
+
| `useAxios(axios)` | Traces an axios object and everything it creates. Calls `init()` first if needed. Returns `this`. |
|
|
17
|
+
| `getTraces()` | Every trace held, oldest first. |
|
|
18
|
+
| `getTrace(id)` | One trace, or `undefined`. |
|
|
19
|
+
| `subscribe(fn)` | Calls `fn(trace)` for each **finished** trace. Returns the unsubscribe function. |
|
|
20
|
+
| `clear()` | Drops every trace. |
|
|
21
|
+
| `remove(id)` | Drops one. |
|
|
22
|
+
| `destroy()` | Restores every patched global and every axios adapter. After this the runtime behaves exactly as it did before `init()`. |
|
|
23
|
+
| `isActive` | Whether tracing is currently installed. |
|
|
24
|
+
|
|
25
|
+
Standalone function forms, for callers who would rather not reach through an
|
|
26
|
+
object: `initApiTracer`, `getApiTraces`, `clearApiTraces`,
|
|
27
|
+
`subscribeToApiTraces`, `destroyApiTracer`. They act on the same shared
|
|
28
|
+
instance.
|
|
29
|
+
|
|
30
|
+
`ApiTracer` is exported as a class too, if you want an isolated instance — most
|
|
31
|
+
useful in tests, where a shared singleton between test cases is a nuisance.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { ApiTracer } from 'api-tracer-kit';
|
|
35
|
+
|
|
36
|
+
const tracer = new ApiTracer().init({ transports: ['fetch'] });
|
|
37
|
+
// ...
|
|
38
|
+
tracer.destroy();
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Options
|
|
42
|
+
|
|
43
|
+
Every one is optional.
|
|
44
|
+
|
|
45
|
+
### `transports`
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
transports?: ('fetch' | 'xhr' | 'axios')[]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Which globals to patch. Default: all of them that exist in the runtime. Pass
|
|
52
|
+
`[]` to patch nothing globally — useful when you only want axios, which is
|
|
53
|
+
attached separately.
|
|
54
|
+
|
|
55
|
+
`'axios'` in this list does nothing on its own; axios has no global to patch and
|
|
56
|
+
is reached only through `useAxios()`.
|
|
57
|
+
|
|
58
|
+
### `include` / `exclude`
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
include?: (string | RegExp)[]
|
|
62
|
+
exclude?: (string | RegExp)[]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Strings match by substring, so `'/api/'` is usually enough. `exclude` is applied
|
|
66
|
+
first and wins. With no `include`, everything not excluded is traced.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
apiTracer.init({ include: ['/api/'], exclude: [/\/health$/, 'analytics'] });
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`reportTo`'s own endpoint is excluded automatically — the tracer never traces
|
|
73
|
+
its own reporting.
|
|
74
|
+
|
|
75
|
+
### `redactHeaders` / `redactFields`
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
redactHeaders?: string[] // default: DEFAULT_REDACT_HEADERS
|
|
79
|
+
redactFields?: RegExp // default: DEFAULT_REDACT_FIELDS
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Header names are matched case-insensitively; field names are matched with the
|
|
83
|
+
regex. Both replace the **value** with `<redacted>` and keep the surrounding
|
|
84
|
+
shape. Redaction happens before a trace is stored, so a real credential is never
|
|
85
|
+
held in memory.
|
|
86
|
+
|
|
87
|
+
Defaults are exported so you can extend rather than replace them:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { DEFAULT_REDACT_HEADERS } from 'api-tracer-kit';
|
|
91
|
+
|
|
92
|
+
apiTracer.init({ redactHeaders: [...DEFAULT_REDACT_HEADERS, 'x-internal-key'] });
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`DEFAULT_REDACT_HEADERS` covers `authorization`, `cookie`, `set-cookie`,
|
|
96
|
+
`proxy-authorization`, `x-api-key`, `api-key`, `auth_token`, `access_token`,
|
|
97
|
+
`secret_token`, `x-auth-token`, `x-csrf-token`.
|
|
98
|
+
|
|
99
|
+
`DEFAULT_REDACT_FIELDS` is
|
|
100
|
+
`/(token|password|passwd|secret|api[-_]?key|authorization|credential|otp|ssn)/i`.
|
|
101
|
+
|
|
102
|
+
### `maxBodyBytes`
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
maxBodyBytes?: number // default 200_000
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Bodies larger than this are recorded as omitted rather than truncated — a
|
|
109
|
+
partial JSON body is more misleading than none. A response declaring a larger
|
|
110
|
+
`content-length` is never read at all.
|
|
111
|
+
|
|
112
|
+
### `maxTraces`
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
maxTraces?: number // default 500
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The in-memory store is a capped ring; older traces fall off the end. An
|
|
119
|
+
unbounded store in a long-lived tab is a memory leak.
|
|
120
|
+
|
|
121
|
+
### `envelope`
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
envelope?: {
|
|
125
|
+
codeFields?: string[]; // default ['status', 'code']
|
|
126
|
+
okField?: string; // default 'success'
|
|
127
|
+
failFrom?: number; // default 400
|
|
128
|
+
} | false
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
For APIs that answer HTTP `200` and put the real verdict in the body:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{ "status": 801, "success": false, "message": "Authentication token header missing" }
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
With an envelope configured, that trace gets `envelopeOk: false` and
|
|
138
|
+
`envelopeCode: 801` even though `response.status` is `200`. Pass an empty object
|
|
139
|
+
`{}` to accept the defaults; omit it entirely and no envelope is read.
|
|
140
|
+
|
|
141
|
+
### `reportTo`
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
reportTo?: string | boolean
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
POST each finished trace to a console. `true` means `http://localhost:4400`.
|
|
148
|
+
Failures are swallowed — a console that is not running must never affect the
|
|
149
|
+
app — and after six consecutive failures the reporter stops trying.
|
|
150
|
+
|
|
151
|
+
### `storage`
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
storage?: TraceStorage
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Replaces the in-memory store. See [Storage](#storage).
|
|
158
|
+
|
|
159
|
+
### `onTrace`
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
onTrace?: (trace: ApiTrace) => void
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Identical to `subscribe()`, set up front. This is the one option a second
|
|
166
|
+
`init()` still honours, because adding a subscriber from another module is
|
|
167
|
+
legitimate.
|
|
168
|
+
|
|
169
|
+
### `debug`
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
debug?: boolean // default false
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Logs a line on init and reports subscribers that throw.
|
|
176
|
+
|
|
177
|
+
## The `ApiTrace` model
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
interface ApiTrace {
|
|
181
|
+
id: string;
|
|
182
|
+
transport: 'fetch' | 'xhr' | 'axios';
|
|
183
|
+
status: 'pending' | 'success' | 'error' | 'network-error' | 'cancelled';
|
|
184
|
+
|
|
185
|
+
request: {
|
|
186
|
+
url: string; // absolute
|
|
187
|
+
path: string; // origin + pathname, for grouping
|
|
188
|
+
method: string;
|
|
189
|
+
headers: Record<string, string>; // redacted
|
|
190
|
+
params: Record<string, unknown>; // parsed query string, redacted
|
|
191
|
+
body?: unknown; // parsed and redacted
|
|
192
|
+
bodyType: 'json' | 'formdata' | 'urlencoded' | 'text' | 'binary' | 'none';
|
|
193
|
+
bodyOmitted?: 'too-large' | 'stream' | 'binary' | 'unreadable';
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
response?: {
|
|
197
|
+
status: number;
|
|
198
|
+
statusText?: string;
|
|
199
|
+
headers: Record<string, string>; // redacted
|
|
200
|
+
body?: string; // raw text
|
|
201
|
+
bodyOmitted?: 'too-large' | 'stream' | 'binary' | 'unreadable';
|
|
202
|
+
size?: number;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
timing: {
|
|
206
|
+
startedAt: number; // performance.now() where available
|
|
207
|
+
completedAt?: number;
|
|
208
|
+
duration?: number; // milliseconds, rounded
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
error?: { message: string; name?: string; stack?: string };
|
|
212
|
+
|
|
213
|
+
envelopeOk?: boolean;
|
|
214
|
+
envelopeCode?: number;
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### What each `status` means
|
|
219
|
+
|
|
220
|
+
| | |
|
|
221
|
+
| --- | --- |
|
|
222
|
+
| `pending` | in flight; `response` and `timing.duration` are not set yet |
|
|
223
|
+
| `success` | HTTP 2xx |
|
|
224
|
+
| `error` | the server answered, but not with a 2xx |
|
|
225
|
+
| `network-error` | no answer: DNS failure, connection refused, timeout |
|
|
226
|
+
| `cancelled` | aborted by the caller — not a broken endpoint |
|
|
227
|
+
|
|
228
|
+
Subscribers are called only when a trace finishes, so a subscriber never sees
|
|
229
|
+
`pending`. `getTraces()` can return one.
|
|
230
|
+
|
|
231
|
+
### Bodies
|
|
232
|
+
|
|
233
|
+
`bodyType` records what actually went on the wire, so a captured call can be
|
|
234
|
+
replayed the same way. A `FormData` is unpacked field by field — `JSON.stringify`
|
|
235
|
+
flattens one to `{}` — and files inside it become
|
|
236
|
+
`<file: scan.pdf, 20418 bytes>`, because a file cannot be replayed.
|
|
237
|
+
|
|
238
|
+
`bodyOmitted` says why a body is missing:
|
|
239
|
+
|
|
240
|
+
| | |
|
|
241
|
+
| --- | --- |
|
|
242
|
+
| `too-large` | over `maxBodyBytes` |
|
|
243
|
+
| `stream` | a `ReadableStream` request body, or a `text/event-stream` response — reading either would break it |
|
|
244
|
+
| `binary` | a Blob, ArrayBuffer, or a non-textual content type |
|
|
245
|
+
| `unreadable` | the body could not be cloned or parsed |
|
|
246
|
+
|
|
247
|
+
## Storage
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
interface TraceStorage {
|
|
251
|
+
add(trace: ApiTrace): void;
|
|
252
|
+
update(id: string, patch: Partial<ApiTrace>): ApiTrace | undefined;
|
|
253
|
+
get(id: string): ApiTrace | undefined;
|
|
254
|
+
getAll(): ApiTrace[];
|
|
255
|
+
remove(id: string): void;
|
|
256
|
+
clear(): void;
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
The default is `MemoryTraceStorage`, exported so you can size it yourself:
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
import { MemoryTraceStorage } from 'api-tracer-kit';
|
|
264
|
+
|
|
265
|
+
apiTracer.init({ storage: new MemoryTraceStorage(50) });
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
`update()` returning `undefined` for an unknown id is normal, not an error: a
|
|
269
|
+
long-running request can be evicted by the cap while still in flight.
|
|
270
|
+
|
|
271
|
+
Nothing is persisted by default. To keep traces across reloads, supply storage
|
|
272
|
+
backed by `sessionStorage` or IndexedDB — but see
|
|
273
|
+
[Security](./security.md) first, because traces contain request and response
|
|
274
|
+
bodies.
|
|
275
|
+
|
|
276
|
+
## axios
|
|
277
|
+
|
|
278
|
+
axios calls are traced two ways.
|
|
279
|
+
|
|
280
|
+
In a browser axios rides on `XMLHttpRequest`, so `apiTracer.init()` alone sees
|
|
281
|
+
them. `useAxios()` adds what the XHR adapter cannot:
|
|
282
|
+
|
|
283
|
+
- the `params` object as axios received it, rather than a re-parsed query string
|
|
284
|
+
- Node's `http` adapter, which no global patch can reach
|
|
285
|
+
- correct handling of axios's habit of rejecting on non-2xx while still having a
|
|
286
|
+
response
|
|
287
|
+
|
|
288
|
+
A call seen by both adapters is recorded **once**. axios builds and opens its
|
|
289
|
+
XHR synchronously inside its adapter, so the tracer raises a flag around the
|
|
290
|
+
adapter call that is still up when `open` runs and down before anything else can
|
|
291
|
+
start a request. The suppression is exact, not time-based.
|
|
292
|
+
|
|
293
|
+
`useAxios()` wraps the instance's adapter rather than only its interceptors,
|
|
294
|
+
because the adapter is the one place both the request and the raw response are
|
|
295
|
+
available. If your application later assigns `axios.defaults.adapter` — a mock,
|
|
296
|
+
a retry library, a custom transport — tracing survives it: the property is an
|
|
297
|
+
accessor that routes the assignment underneath the wrapper.
|
|
298
|
+
|
|
299
|
+
`destroy()` gives every instance back the adapter it last had, including
|
|
300
|
+
instances created through the patched `create()`.
|
|
301
|
+
|
|
302
|
+
## What the tracer will not do to your app
|
|
303
|
+
|
|
304
|
+
Removing it changes nothing. Specifically:
|
|
305
|
+
|
|
306
|
+
- responses are read through `Response.clone()`, **after** the original has been
|
|
307
|
+
handed back, so your `.json()` still works
|
|
308
|
+
- `text/event-stream` responses are never buffered
|
|
309
|
+
- binary and non-textual responses are described, not read
|
|
310
|
+
- request bodies that are streams are never consumed
|
|
311
|
+
- errors are rethrown exactly as they arrived; aborts stay `AbortError`
|
|
312
|
+
- `AbortSignal`, redirects, credentials and headers pass through untouched
|
|
313
|
+
- nothing is retried, and no request is made twice
|
|
314
|
+
- XHR is observed with `addEventListener`, never by taking `onload` or
|
|
315
|
+
`onreadystatechange` — those belong to the caller
|
|
316
|
+
- a subscriber that throws is caught
|
|
317
|
+
- `destroy()` restores every patched global
|
|
318
|
+
|
|
319
|
+
## Cleanup
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
apiTracer.destroy();
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Restores `fetch`, the `XMLHttpRequest` prototype and every axios adapter, clears
|
|
326
|
+
the subscribers, and stops reporting. `fetch` is only restored if nothing else
|
|
327
|
+
has patched it since — clobbering another tool's interceptor on the way out
|
|
328
|
+
would be worse than leaving ours in place.
|
|
329
|
+
|
|
330
|
+
Calling `destroy()` on a tracer that was never started is safe.
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
## Nothing is being recorded
|
|
4
|
+
|
|
5
|
+
Work down this list in order. Each step tells you which link in the chain is
|
|
6
|
+
broken, so you never have to guess.
|
|
7
|
+
|
|
8
|
+
### 1. Is the tracer running at all?
|
|
9
|
+
|
|
10
|
+
Open your app and look at the **browser** console. With `debug: true` or a log
|
|
11
|
+
of your own you should see the tracer start. If you see nothing, `init()` never
|
|
12
|
+
ran — the package is installed but nothing imported it.
|
|
13
|
+
|
|
14
|
+
**Installing a package does not run it.** Something must import the module that
|
|
15
|
+
calls `init()`, and that module must be reachable from your entry point:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
// src/index.js
|
|
19
|
+
import './services/apiRecorder';
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Check the module is actually in the bundle:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
curl -s http://localhost:3000/static/js/bundle.js | grep -c 'api-tracer-kit'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Zero means it is not being compiled in.
|
|
29
|
+
|
|
30
|
+
### 2. Is it configured the way you think?
|
|
31
|
+
|
|
32
|
+
Inspect the live tracer from the browser console:
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
const t = globalThis[Symbol.for('api-tracer-kit.registry')].tracer;
|
|
36
|
+
({ active: t.isActive, patches: t.uninstallers.length, excludes: t.options.exclude.map(String) });
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
| What you see | What it means |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| `active: false` | `init()` never ran |
|
|
42
|
+
| `excludes: []` when you passed `reportTo` | **your options were ignored — see below** |
|
|
43
|
+
| `patches: 3` when you passed `transports: []` | same cause |
|
|
44
|
+
|
|
45
|
+
**The most common cause of a tracer that runs but reports nothing is a second
|
|
46
|
+
`init()`.** It is idempotent — a second call keeps the first one's configuration
|
|
47
|
+
so it cannot install a second set of interceptors and double-count every
|
|
48
|
+
request. Since 1.0.1 it warns:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
[api-tracer] init() was called again on a tracer that is already running, so these
|
|
52
|
+
options were ignored: reportTo, transports, envelope.
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Find every call and keep exactly one:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
grep -rn 'apiTracer.init\|initApiTracer' src/
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 3. Is your app actually making API calls?
|
|
62
|
+
|
|
63
|
+
An app sitting on a login screen may make none. Check the Network tab, or:
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
globalThis[Symbol.for('api-tracer-kit.registry')].tracer.getTraces().length;
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Traces above zero means the tracer works and the problem is downstream — go to
|
|
70
|
+
step 5. Zero traces with calls visible in the Network tab means the transport
|
|
71
|
+
carrying them is not patched — step 4.
|
|
72
|
+
|
|
73
|
+
### 4. Is the right transport patched?
|
|
74
|
+
|
|
75
|
+
`transports: []` patches **nothing globally**. That is correct when you only
|
|
76
|
+
want axios, but it means plain `fetch` and `XMLHttpRequest` calls are invisible.
|
|
77
|
+
|
|
78
|
+
axios instances need `useAxios()`; there is no global to patch. And it must
|
|
79
|
+
cover the instance your code actually uses — `useAxios(axios)` covers the
|
|
80
|
+
default export and everything `axios.create()` makes **after** that call, so
|
|
81
|
+
`init()` has to run before your API layer creates its instances.
|
|
82
|
+
|
|
83
|
+
### 5. Are traces reaching the console?
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
curl -s http://127.0.0.1:4400/api/live | python3 -c 'import json,sys; print(json.load(sys.stdin)["count"])'
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Still zero with traces in memory? The reporter is failing. It fails **silently
|
|
90
|
+
by design** — a console that is not running must never affect your app — so look
|
|
91
|
+
at the Network tab for the POST to `/api/record`.
|
|
92
|
+
|
|
93
|
+
| | |
|
|
94
|
+
| --- | --- |
|
|
95
|
+
| No request at all | `reportTo` is not set. Back to step 2. |
|
|
96
|
+
| `404` | Path mismatch. If `reportTo` is `/api-console`, the console must run with `BASE_PATH=/api-console`. |
|
|
97
|
+
| CORS error | Add your app's origin to `ALLOWED_ORIGINS`. Localhost always works. |
|
|
98
|
+
| `ERR_CONNECTION_REFUSED` | The console is not running, or is on a different port. |
|
|
99
|
+
| Blocked, mixed content | An HTTPS app cannot post to an `http://` console. Serve the console over TLS or under a path on the app's own origin. |
|
|
100
|
+
|
|
101
|
+
### 6. Are you looking at the right app?
|
|
102
|
+
|
|
103
|
+
If port 3000 was taken, CRA silently starts on 3001 — and you may be browsing a
|
|
104
|
+
different project entirely:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
lsof -nP -iTCP:3000 -sTCP:LISTEN
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## The scan finds no endpoints
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npx api-tracer init
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
It prints what each preset found:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
service-object 325
|
|
120
|
+
fetch-direct 1
|
|
121
|
+
axios-direct 0
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
All zero means no preset reads your codebase. Check `sources` covers where your
|
|
125
|
+
API layer lives, then either force one with `--preset`, or write a `parse`
|
|
126
|
+
function — see [Configuration](./configuration.md#your-own-scanner).
|
|
127
|
+
|
|
128
|
+
Live recording works without a catalog: every call is adopted as an endpoint of
|
|
129
|
+
its own.
|
|
130
|
+
|
|
131
|
+
## Endpoints list, but nothing is captured
|
|
132
|
+
|
|
133
|
+
Those are independent. The list comes from the console reading your source; the
|
|
134
|
+
captures come from your app posting traffic. A working list tells you nothing
|
|
135
|
+
about the tracer. Go to step 1 above.
|
|
136
|
+
|
|
137
|
+
## `Module not found: ... falls outside of the project src/ directory`
|
|
138
|
+
|
|
139
|
+
CRA's `ModuleScopePlugin`. You imported by relative path. Import by package
|
|
140
|
+
name; see [Frameworks → CRA](./frameworks.md#create-react-app).
|
|
141
|
+
|
|
142
|
+
## `Package "api-tracer-kit" refers to a non-existing file`
|
|
143
|
+
|
|
144
|
+
Yarn 1 resolving a relative `file:` path wrongly. Use a packed tarball, or a
|
|
145
|
+
published version. See [Frameworks → CRA](./frameworks.md#create-react-app).
|
|
146
|
+
|
|
147
|
+
## A second React, or "invalid hook call"
|
|
148
|
+
|
|
149
|
+
`yarn add file:<directory>` copies the whole directory, **including its own
|
|
150
|
+
`node_modules`** — React among them. Two Reacts in one build breaks hooks. A
|
|
151
|
+
registry install or a packed tarball ships only what `files` allows.
|
|
152
|
+
|
|
153
|
+
## Every call is recorded twice
|
|
154
|
+
|
|
155
|
+
Two `init()` calls on separate instances, or a second copy of the package in the
|
|
156
|
+
build. The shared tracer is held in a global registry so entry points agree; a
|
|
157
|
+
duplicate usually means two different versions are installed.
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npm ls api-tracer-kit
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
axios calls are **not** a cause: a call seen by both the axios and XHR adapters
|
|
164
|
+
is recorded once.
|
|
165
|
+
|
|
166
|
+
## Response bodies are missing
|
|
167
|
+
|
|
168
|
+
Check `response.bodyOmitted`:
|
|
169
|
+
|
|
170
|
+
| | |
|
|
171
|
+
| --- | --- |
|
|
172
|
+
| `too-large` | over `maxBodyBytes` (default 200 kB). Raise it if you need them. |
|
|
173
|
+
| `stream` | `text/event-stream`. Never buffered, deliberately. |
|
|
174
|
+
| `binary` | a non-textual content type. Described, not read. |
|
|
175
|
+
| `unreadable` | the body could not be cloned or parsed. |
|
|
176
|
+
|
|
177
|
+
## A response body is `<redacted>`
|
|
178
|
+
|
|
179
|
+
A field whose **name** matched `redactFields`, or a header matching
|
|
180
|
+
`redactHeaders`. That is working as intended. Narrow the pattern if it is too
|
|
181
|
+
eager — but see [Security](./security.md) first.
|
|
182
|
+
|
|
183
|
+
## The app broke after adding the tracer
|
|
184
|
+
|
|
185
|
+
It should not have; that is the package's first design constraint. Confirm by
|
|
186
|
+
removing it:
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
apiTracer.destroy();
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
That restores every patched global and axios adapter. If the problem survives
|
|
193
|
+
`destroy()`, it is not the tracer. If it does not, please open an issue with the
|
|
194
|
+
transport and the call shape.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "api-tracer-kit",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Trace every API call an app makes, and map, exercise and monitor the endpoints it has.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"files": [
|
|
51
51
|
"dist",
|
|
52
52
|
"cli",
|
|
53
|
+
"docs",
|
|
53
54
|
"README.md",
|
|
54
55
|
"LICENSE",
|
|
55
56
|
"CHANGELOG.md"
|