@hakam-aldeen-kh/blix 0.4.2 → 0.6.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/README.md +298 -37
- package/dist/{DevToolsPanel-TXMT43YN.js → DevToolsPanel-QTVFBNU5.js} +3651 -2178
- package/dist/capture/index.d.ts +168 -18
- package/dist/capture/index.js +4 -2
- package/dist/chunk-4JZEVUOR.js +735 -0
- package/dist/{chunk-MRTSIXR7.js → chunk-FIYP47QZ.js} +202 -118
- package/dist/index.d.ts +4 -4
- package/dist/index.js +13 -4
- package/package.json +2 -1
- package/dist/chunk-OQHKPRNI.js +0 -287
package/README.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
An in-app dev-tools panel for React apps. Captures HTTP requests, Redux
|
|
5
5
|
actions, TanStack Query cache events and realtime traffic, and renders them in
|
|
6
|
-
a dockable panel with
|
|
6
|
+
a dockable panel with cross-source links, diffing, replay and HAR/cURL
|
|
7
|
+
export.
|
|
7
8
|
|
|
8
9
|
The entire panel is eliminated from production builds — see
|
|
9
10
|
[Production elimination](#production-elimination).
|
|
@@ -44,8 +45,15 @@ no reason to carry a second build output.
|
|
|
44
45
|
`react` and `react-dom` (v19) are required. `axios`, `@reduxjs/toolkit` and
|
|
45
46
|
`@tanstack/react-query` are **optional** peers — you only need the ones whose
|
|
46
47
|
capture you actually use. The capture layer is structurally typed against each
|
|
47
|
-
of them and
|
|
48
|
-
pull a data-fetching or state
|
|
48
|
+
of them and imports none of them — not at runtime, and not in its published
|
|
49
|
+
type declarations — so installing Blix does not pull a data-fetching or state
|
|
50
|
+
library into your tree, and your type-check never goes looking for one.
|
|
51
|
+
|
|
52
|
+
**axios is optional, and fetch-only apps are fully supported.** An app that
|
|
53
|
+
calls `fetch` directly — the default in a Next.js App Router project — captures
|
|
54
|
+
its HTTP traffic with
|
|
55
|
+
[`attachFetchMonitor`](#http-fetch--attachfetchmonitoroptions) and never needs
|
|
56
|
+
axios installed, not even for types.
|
|
49
57
|
|
|
50
58
|
---
|
|
51
59
|
|
|
@@ -73,6 +81,7 @@ it.
|
|
|
73
81
|
| Function | Where to call it | Timing |
|
|
74
82
|
| --- | --- | --- |
|
|
75
83
|
| `attachHttpMonitor` | after your own interceptors are registered on the instance | module scope |
|
|
84
|
+
| `attachFetchMonitor` | before the first `fetch` you want captured — client-side only | module scope |
|
|
76
85
|
| `createReduxMonitorMiddleware` | in `configureStore`'s `middleware` callback | module scope |
|
|
77
86
|
| `tapRealtimeAdapter` | where the adapter singleton is constructed | module scope |
|
|
78
87
|
| `tapQueryClient` | a `useEffect` in your query provider | see below |
|
|
@@ -133,6 +142,12 @@ if (process.env.NODE_ENV === "development" && typeof window !== "undefined") {
|
|
|
133
142
|
}
|
|
134
143
|
```
|
|
135
144
|
|
|
145
|
+
`attachHttpMonitor` is idempotent per instance — a second call on the same
|
|
146
|
+
instance, or on its `withInitiatorCapture` wrapper, does nothing — and it
|
|
147
|
+
returns a disposer that ejects both interceptors, for HMR and tests. A request
|
|
148
|
+
cancelled through an `AbortController` or a `CancelToken` settles as
|
|
149
|
+
**aborted**, not as an error; a timeout is still an error.
|
|
150
|
+
|
|
136
151
|
#### Factory and lazy-singleton clients
|
|
137
152
|
|
|
138
153
|
The rule is **causal, not positional**. "Bottom of the module" is shorthand
|
|
@@ -173,8 +188,8 @@ instance.interceptors.response.use(undefined, (error) =>
|
|
|
173
188
|
|
|
174
189
|
The consequence is silent and total: **every non-2xx request stays `pending`
|
|
175
190
|
in the panel for the rest of the session.** No error, no warning, no Failed
|
|
176
|
-
filter. (The 30-second pending cap only bounds
|
|
177
|
-
it does not resolve the entry.)
|
|
191
|
+
filter. (The 30-second pending cap only bounds how far the row's duration bar
|
|
192
|
+
grows; it does not resolve the entry.)
|
|
178
193
|
|
|
179
194
|
Two ways out, and you currently have to choose one:
|
|
180
195
|
|
|
@@ -200,6 +215,11 @@ request bodies and correlated errors while also discarding the `AxiosError`.
|
|
|
200
215
|
|
|
201
216
|
*Since 0.3.0.*
|
|
202
217
|
|
|
218
|
+
> **axios only.** Correlation works through the config object your own
|
|
219
|
+
> interceptor is holding, and `fetch` has no interceptor stage to hold one in,
|
|
220
|
+
> so `captureEncrypted` has no effect on requests captured by
|
|
221
|
+
> [`attachFetchMonitor`](#http-fetch--attachfetchmonitoroptions).
|
|
222
|
+
|
|
203
223
|
**Entirely optional.** An app that never calls it behaves exactly as it did
|
|
204
224
|
before this API existed, and its panel shows no Encrypted tab at all — the tab
|
|
205
225
|
appears only on entries that actually carry ciphertext.
|
|
@@ -385,7 +405,7 @@ both. There is no wrong choice here and no silent failure.
|
|
|
385
405
|
|
|
386
406
|
What *does* matter is which one your app calls through. Only the proxy's traps
|
|
387
407
|
record a stack, so every request made against the unwrapped instance is still
|
|
388
|
-
captured but arrives with
|
|
408
|
+
captured but arrives with no initiator. Export the wrapped one and
|
|
389
409
|
keep the original private:
|
|
390
410
|
|
|
391
411
|
```ts
|
|
@@ -401,13 +421,131 @@ The traps cover the callable form (`apiClient(config)`) plus `request`, `get`,
|
|
|
401
421
|
the `*Form` helpers — pass through unwrapped: still captured, just with no
|
|
402
422
|
initiator stack.
|
|
403
423
|
|
|
404
|
-
> **Known limitation.** The
|
|
424
|
+
> **Known limitation.** The initiator stack is produced by filtering your own
|
|
405
425
|
> HTTP wrapper's frames out of the captured stack, and that filter currently
|
|
406
426
|
> matches a fixed set of module paths rather than deriving them from where
|
|
407
427
|
> `withInitiatorCapture` was called. If your axios module does not sit at one
|
|
408
428
|
> of those paths, the top frame reported will be your own wrapper rather than
|
|
409
429
|
> the true call site. There is no option to extend the filter yet.
|
|
410
430
|
|
|
431
|
+
### HTTP (fetch) — `attachFetchMonitor(options?)`
|
|
432
|
+
|
|
433
|
+
For requests made with `fetch` directly rather than through axios. It wraps
|
|
434
|
+
`globalThis.fetch`, so it captures **every** `fetch` the page makes — your own
|
|
435
|
+
and any library's or third-party script's — into the same Network section, in
|
|
436
|
+
the same shape, with the same header masking and the same absence of body
|
|
437
|
+
redaction (see [Security](#security)). Its rows show **Client: fetch** in the
|
|
438
|
+
Headers tab, and `client:fetch` filters to them. Each row records the call
|
|
439
|
+
stack of the `fetch` call that made it; no `withInitiatorCapture` is needed.
|
|
440
|
+
|
|
441
|
+
```ts
|
|
442
|
+
// instrumentation-client.ts (Next.js 15.3+), or the first module your client bundle evaluates
|
|
443
|
+
import { attachFetchMonitor } from "@hakam-aldeen-kh/blix/capture";
|
|
444
|
+
|
|
445
|
+
if (process.env.NODE_ENV === "development" && typeof window !== "undefined") {
|
|
446
|
+
attachFetchMonitor();
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
**Call it before the first `fetch` you want captured.** There are no
|
|
451
|
+
interceptors to order against, but anything fetched before the wrapper is
|
|
452
|
+
installed is simply not seen. In a Next.js App Router project,
|
|
453
|
+
`instrumentation-client.ts` runs on the client before your application code and
|
|
454
|
+
is the natural place; otherwise, the top of the first module your client bundle
|
|
455
|
+
evaluates.
|
|
456
|
+
|
|
457
|
+
**Client-side only.** It never patches `fetch` on the server, so Next.js's
|
|
458
|
+
server-side `fetch` and its data cache are untouched — and like every capture
|
|
459
|
+
function, it is a no-op in production.
|
|
460
|
+
|
|
461
|
+
It returns a **disposer** that restores the original `fetch`, and calling
|
|
462
|
+
`attachFetchMonitor` again while it is installed does nothing. The disposer
|
|
463
|
+
only restores `fetch` while Blix's wrapper is still the current one: if another
|
|
464
|
+
library has wrapped `fetch` since, restoring would silently remove that wrapper
|
|
465
|
+
too, so it leaves `fetch` alone and warns in development instead.
|
|
466
|
+
|
|
467
|
+
#### Bodies, streams and the size cap
|
|
468
|
+
|
|
469
|
+
Blix reads bodies from clones, so your code always receives an untouched
|
|
470
|
+
`Request` and `Response`. It never buffers without a limit:
|
|
471
|
+
|
|
472
|
+
| Situation | What is recorded |
|
|
473
|
+
| --- | --- |
|
|
474
|
+
| `Content-Type: text/event-stream` | status, headers, timing — body not captured, because the stream does not end |
|
|
475
|
+
| `Content-Length` over the cap | status, headers, timing — body not captured, reading never starts |
|
|
476
|
+
| body passes the cap while downloading | status, headers, timing — body not captured, reading abandoned |
|
|
477
|
+
| `no-cors` (opaque) response | status `0` — headers and body not captured, because the browser hides them |
|
|
478
|
+
| request body is a `ReadableStream` | request body not captured — reading it would consume the upload |
|
|
479
|
+
|
|
480
|
+
A skipped body is recorded as `«body not captured: …»` with the reason, so an
|
|
481
|
+
empty Response tab always means an empty response. The cap applies to request
|
|
482
|
+
and response bodies alike, defaults to **5 MB**, and is set with
|
|
483
|
+
`maxBodyBytes`:
|
|
484
|
+
|
|
485
|
+
```ts
|
|
486
|
+
attachFetchMonitor({ maxBodyBytes: 1024 * 1024 }); // 1 MB — 0 captures no bodies
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
The cap is enforced by **counting bytes as they arrive**, not by trusting
|
|
490
|
+
`Content-Length`. That header is the compressed size, and a small gzipped
|
|
491
|
+
response can decode to many times it; a declared length over the cap only lets
|
|
492
|
+
Blix skip without starting. A row's duration runs to the end of the body when
|
|
493
|
+
Blix read it, and to the response headers when it did not.
|
|
494
|
+
|
|
495
|
+
A non-2xx response is recorded as a failed request with its body under
|
|
496
|
+
**Error**, the same way an axios rejection is. An aborted request settles as
|
|
497
|
+
**aborted**; a network failure or an `AbortSignal.timeout()` settles as an
|
|
498
|
+
error. Either way the original rejection reaches your code untouched.
|
|
499
|
+
|
|
500
|
+
#### What is left out by default
|
|
501
|
+
|
|
502
|
+
A development server makes plenty of `fetch` calls of its own, and they would
|
|
503
|
+
bury your requests. These are **not captured by default**:
|
|
504
|
+
|
|
505
|
+
| Rule | Matches |
|
|
506
|
+
| --- | --- |
|
|
507
|
+
| `"/_next/"` | Next.js assets, HMR updates and Pages Router data requests |
|
|
508
|
+
| `"/__nextjs"` | the Next.js dev overlay — stack frames, source maps, open-in-editor |
|
|
509
|
+
| `/[?&]_rsc=/` | App Router navigations and `<Link>` prefetches |
|
|
510
|
+
| `".hot-update."` | webpack HMR update manifests and chunks |
|
|
511
|
+
| `"/__webpack_hmr"` | webpack-hot-middleware |
|
|
512
|
+
|
|
513
|
+
A string matches anywhere in the URL's path and query, a regular expression is
|
|
514
|
+
tested against the same string, and a function receives the resolved `URL`.
|
|
515
|
+
Pass an array to **replace** the defaults, or a function to **extend** them:
|
|
516
|
+
|
|
517
|
+
```ts
|
|
518
|
+
attachFetchMonitor({ ignore: (defaults) => [...defaults, "/api/health"] }); // extend
|
|
519
|
+
attachFetchMonitor({ ignore: [] }); // capture everything
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
**An ignored request is dropped silently** — no row records that it was
|
|
523
|
+
skipped. If a request you expected is missing, check it against this list
|
|
524
|
+
first.
|
|
525
|
+
|
|
526
|
+
#### Using it alongside axios
|
|
527
|
+
|
|
528
|
+
Installing both `attachFetchMonitor` and `attachHttpMonitor` is fine. axios's
|
|
529
|
+
default browser adapter is XHR, which the fetch wrapper never sees. If you
|
|
530
|
+
configure axios with `adapter: "fetch"`, one request passes through both — and
|
|
531
|
+
**the axios entry wins**. It was captured on the plaintext side of your
|
|
532
|
+
interceptors, keeps `captureEncrypted` and `withInitiatorCapture`, and can be
|
|
533
|
+
replayed; the fetch wrapper would only see the same request after encryption.
|
|
534
|
+
`attachHttpMonitor` marks each request on its way into axios's fetch adapter,
|
|
535
|
+
and the fetch wrapper skips anything carrying the mark, so every request is
|
|
536
|
+
logged exactly once. The order of the two calls does not matter.
|
|
537
|
+
|
|
538
|
+
#### What it does not do
|
|
539
|
+
|
|
540
|
+
- **No replay.** Fetch rows show Replay disabled, with the reason. Blix does
|
|
541
|
+
not record a call's `credentials`, `mode` or `cache`, or a JSON body as the
|
|
542
|
+
exact bytes that were sent, so a replay could not promise to be the same
|
|
543
|
+
request.
|
|
544
|
+
- **No `captureEncrypted`** — see
|
|
545
|
+
[Encrypted payloads](#encrypted-payloads--captureencryptedconfig-payload).
|
|
546
|
+
- **No timeout.** A request that never answers stays pending, exactly as an
|
|
547
|
+
axios request does.
|
|
548
|
+
|
|
411
549
|
### Redux — `createReduxMonitorMiddleware(options?)`
|
|
412
550
|
|
|
413
551
|
```ts
|
|
@@ -545,7 +683,7 @@ import { store } from "@/src/store";
|
|
|
545
683
|
// that evaluation on the client side of the boundary.
|
|
546
684
|
export default function BlixMount() {
|
|
547
685
|
if (process.env.NODE_ENV !== "development") return null;
|
|
548
|
-
return <Blix store={store} apiClient={apiClient} dbName="my-app
|
|
686
|
+
return <Blix store={store} apiClient={apiClient} dbName="my-app" />;
|
|
549
687
|
}
|
|
550
688
|
```
|
|
551
689
|
|
|
@@ -589,7 +727,7 @@ boundary. That is the reason for the split entry point: import capture from
|
|
|
589
727
|
| --- | --- |
|
|
590
728
|
| `store` | The **State** tab renders `— Redux store not provided —`, and **Re-dispatch** is disabled with the reason `Redux store not provided`. Everything else works. |
|
|
591
729
|
| `apiClient` | **Replay request** is disabled with the reason `HTTP client not provided`. Everything else works. |
|
|
592
|
-
| `dbName` |
|
|
730
|
+
| `dbName` | Falls back to the shared database `blix:default`, and the panel warns in the console. See [`dbName`](#dbname--when-you-need-it). |
|
|
593
731
|
|
|
594
732
|
`store` and `apiClient` are structurally typed — they need
|
|
595
733
|
`getState`/`subscribe`/`dispatch` and `request` respectively. A redux-toolkit
|
|
@@ -601,6 +739,39 @@ structural typing means you just pass your store and client.)
|
|
|
601
739
|
Passing neither still gives you a fully working capture log; you only lose the
|
|
602
740
|
two features that need a live handle on the app.
|
|
603
741
|
|
|
742
|
+
### Finding your way around
|
|
743
|
+
|
|
744
|
+
The panel is one header, one rail and two panes.
|
|
745
|
+
|
|
746
|
+
**The rail on the left is the four sources** — Network, Realtime, Redux,
|
|
747
|
+
Query. They are not filters over one table: each has its own columns and its
|
|
748
|
+
own notion of a row, so switching source switches the whole view. `1`–`4` jump
|
|
749
|
+
between them, and each keeps its own selection, so stepping to Redux and back
|
|
750
|
+
returns you to the request you were reading. Below the sources it carries the
|
|
751
|
+
session totals, and it collapses to icons — click the chevron, or let a narrow
|
|
752
|
+
dock do it for you.
|
|
753
|
+
|
|
754
|
+
**The header is the session**, not the entry: whether capture is running, what
|
|
755
|
+
is being filtered out, and where the panel lives. Filter tokens you have
|
|
756
|
+
already applied become chips *inside* the filter field, each removable on its
|
|
757
|
+
own, so `method:post status:5xx` is two things you can undo separately rather
|
|
758
|
+
than one string to re-edit. `.*` widens the search to request and response
|
|
759
|
+
bodies.
|
|
760
|
+
|
|
761
|
+
**`Ctrl/⌘ K` opens the command palette**, and for several things it is the only
|
|
762
|
+
way in — sort order, row density, dock position, the copy formats, the filter
|
|
763
|
+
syntax. The header spends its width on what you read constantly; everything you
|
|
764
|
+
reach for occasionally lives one keystroke away instead of costing a button
|
|
765
|
+
each. Every row shows its key binding where it has one, so the palette teaches
|
|
766
|
+
its own shortcuts. `?` still opens the full cheatsheet.
|
|
767
|
+
|
|
768
|
+
**Linked events tie the sources together.** When Blix can see that a query
|
|
769
|
+
caused a request, that a request came from a query, or that one entry is a
|
|
770
|
+
replay of another, the row grows a coloured tick and the foot of the detail
|
|
771
|
+
pane grows a chip you can click to step straight to the other side. The
|
|
772
|
+
relation is observed, never inferred: no tick means *not known*, not
|
|
773
|
+
*unrelated*.
|
|
774
|
+
|
|
604
775
|
### Viewing payloads
|
|
605
776
|
|
|
606
777
|
Every payload pane has a format switch. The choice is remembered, so you pick
|
|
@@ -634,7 +805,7 @@ literal rather than a pre-serialized string.
|
|
|
634
805
|
|
|
635
806
|
### Exporting the log
|
|
636
807
|
|
|
637
|
-
The
|
|
808
|
+
The **Export** button in the header offers six formats and a scope
|
|
638
809
|
toggle — **Shown** (what the current section and filters leave visible) or
|
|
639
810
|
**All**. It defaults to Shown, with both counts on the control, so an export
|
|
640
811
|
says what it will contain before you pick a format.
|
|
@@ -653,7 +824,8 @@ placeholder rather than a working token — use **Replay** for a real re-run.
|
|
|
653
824
|
|
|
654
825
|
### Themes
|
|
655
826
|
|
|
656
|
-
Twelve themes, under the
|
|
827
|
+
Twelve themes, under the theme button in the header — it names the one
|
|
828
|
+
currently applied:
|
|
657
829
|
|
|
658
830
|
| | Theme | |
|
|
659
831
|
| --- | --- | --- |
|
|
@@ -682,7 +854,7 @@ panel's other preferences and survives a reload.
|
|
|
682
854
|
The panel never inherits your app's styling — it portals outside every stacking
|
|
683
855
|
context and ships its own palettes, so nothing you do to your own theme can
|
|
684
856
|
distort it. Themes are complete rather than partial: every colour the panel
|
|
685
|
-
paints, down to the JSON syntax highlighting and the
|
|
857
|
+
paints, down to the JSON syntax highlighting and the duration bars, comes from
|
|
686
858
|
the active theme. Each palette is checked against WCAG contrast targets — 4.5:1
|
|
687
859
|
for anything read as text, 3:1 for badges and quiet chrome — which is why a few
|
|
688
860
|
of the ported palettes differ by a shade from the originals in the slots used
|
|
@@ -698,27 +870,68 @@ when you opt in: **preserve-log is off by default**, and while it is off
|
|
|
698
870
|
nothing is written to disk. See [Security](#security) for what the toggle does
|
|
699
871
|
and what lands there.
|
|
700
872
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
873
|
+
**All of Blix's storage is scoped to the origin, not to your app.** That is how
|
|
874
|
+
IndexedDB and `localStorage` both work, and it covers the captured log *and*
|
|
875
|
+
your panel preferences — dock position, theme, density, the preserve-log
|
|
876
|
+
toggle. Two apps served from the same origin (different ports in dev are
|
|
877
|
+
different origins, but path-based routing, multi-zone Next.js setups and
|
|
878
|
+
anything behind one reverse proxy are not) share every one of them: entries
|
|
879
|
+
from one project appear in the other's panel, and whichever you opened last
|
|
880
|
+
decides where the panel is docked.
|
|
705
881
|
|
|
706
|
-
Give each app its own
|
|
882
|
+
`dbName` is how projects on one origin are kept apart. Give each app its own:
|
|
707
883
|
|
|
708
884
|
```tsx
|
|
709
|
-
<Blix store={store} apiClient={apiClient} dbName="checkout
|
|
885
|
+
<Blix store={store} apiClient={apiClient} dbName="checkout" />
|
|
710
886
|
```
|
|
711
887
|
|
|
712
888
|
You can also set it from the capture side, which is useful when capture starts
|
|
713
889
|
before the panel mounts:
|
|
714
890
|
|
|
715
891
|
```ts
|
|
716
|
-
attachHttpMonitor(apiClient, { dbName: "checkout
|
|
892
|
+
attachHttpMonitor(apiClient, { dbName: "checkout" });
|
|
717
893
|
```
|
|
718
894
|
|
|
719
895
|
Either call must happen before the database is first opened, which the panel
|
|
720
896
|
does on mount. If both are set, the `<Blix />` prop wins, since render runs
|
|
721
|
-
after module init.
|
|
897
|
+
after module init. A call that arrives after the database is open is ignored —
|
|
898
|
+
Blix does not switch databases at runtime — and says so in the console rather
|
|
899
|
+
than failing quietly.
|
|
900
|
+
|
|
901
|
+
The name you pass is prefixed: `dbName="checkout"` gives you the database
|
|
902
|
+
`blix:checkout` and the preferences key `blix:checkout:prefs`. Passing an
|
|
903
|
+
already-prefixed name is fine and does not double it. Omitting `dbName`
|
|
904
|
+
entirely gives you `blix:default`, shared with every other app on the origin
|
|
905
|
+
that also omits it — and in development the panel warns once per mount when
|
|
906
|
+
that happens, naming the fix.
|
|
907
|
+
|
|
908
|
+
Renaming is not a migration: the old database is left where it is rather than
|
|
909
|
+
moved or deleted, and the new one starts empty at default preferences.
|
|
910
|
+
|
|
911
|
+
#### Databases on this origin
|
|
912
|
+
|
|
913
|
+
The status bar always shows which database the panel is on. On the shared
|
|
914
|
+
default it turns amber and adds a `shared` tag, which is the visible form of
|
|
915
|
+
the console warning above — click it to open the screen below. The same screen
|
|
916
|
+
is in **⋯ More actions** and in the command palette.
|
|
917
|
+
|
|
918
|
+
Open the command palette (`Ctrl/⌘ K`) → **Databases on this origin** to see
|
|
919
|
+
every Blix database the origin holds — one per project, plus `nm-devtools`,
|
|
920
|
+
the single unprefixed database that all projects shared before names were
|
|
921
|
+
prefixed. Each row shows an approximate size and can be deleted; the one this
|
|
922
|
+
panel is using is marked and is not deletable from there, since it is open —
|
|
923
|
+
use **Purge saved log** for that.
|
|
924
|
+
|
|
925
|
+
**Peek** on a row lists the 50 newest entries in that database — time, method,
|
|
926
|
+
URL and status — so you can tell whose log it is before deleting it. It is a
|
|
927
|
+
read-only snapshot: Blix opens the database, reads, and closes it again, so the
|
|
928
|
+
list does not update and there is no detail pane, replay or export. The panel
|
|
929
|
+
itself always stays on its own database; to work with another project's log
|
|
930
|
+
properly, run that project and open the panel there.
|
|
931
|
+
|
|
932
|
+
The screen enumerates with `indexedDB.databases()`, which Firefox does not
|
|
933
|
+
implement. There it falls back to the databases Blix has itself opened in that
|
|
934
|
+
browser and labels the list as possibly incomplete.
|
|
722
935
|
|
|
723
936
|
---
|
|
724
937
|
|
|
@@ -811,6 +1024,11 @@ response body *after* your decryption interceptor. That is the whole point of
|
|
|
811
1024
|
it, and it means the log holds whatever your traffic holds, credentials
|
|
812
1025
|
included.
|
|
813
1026
|
|
|
1027
|
+
`attachFetchMonitor` widens that to **every `fetch` the page makes**, not only
|
|
1028
|
+
your own: an analytics snippet, a chat widget or a library calling `fetch`
|
|
1029
|
+
under the hood is captured the same way, bodies included. Use its `ignore`
|
|
1030
|
+
option to keep a third party's traffic out of the log.
|
|
1031
|
+
|
|
814
1032
|
By default all of that is **in memory only**. Nothing is written to disk, and
|
|
815
1033
|
a reload starts clean.
|
|
816
1034
|
|
|
@@ -825,15 +1043,16 @@ the database is opened only when the panel mounts — see
|
|
|
825
1043
|
while it is off nothing Blix captures reaches IndexedDB — the store is
|
|
826
1044
|
actively cleared on every panel mount.
|
|
827
1045
|
|
|
828
|
-
|
|
1046
|
+
Four ways to toggle it:
|
|
829
1047
|
|
|
830
1048
|
| Where | Note |
|
|
831
1049
|
| --- | --- |
|
|
832
|
-
|
|
|
1050
|
+
| Header button | Keeps its icon at every width; loses its label when the panel is narrow |
|
|
833
1051
|
| **⋯ More actions** overflow menu | — |
|
|
1052
|
+
| Command palette (`Ctrl/⌘ K`) | Listed as **Preserve log across reloads** / **Stop preserving the log** |
|
|
834
1053
|
| `Shift+L` | — |
|
|
835
1054
|
|
|
836
|
-
All
|
|
1055
|
+
All four require the panel to be mounted.
|
|
837
1056
|
|
|
838
1057
|
**Turning it on is retroactive.** The toggle does not mean "from now on".
|
|
839
1058
|
Switching it on writes every entry already sitting in the live buffer — the
|
|
@@ -850,10 +1069,15 @@ With preserve-log on, this is what is kept:
|
|
|
850
1069
|
| HTTP entries — bodies, headers, timings | yes |
|
|
851
1070
|
| Realtime frames | yes |
|
|
852
1071
|
| The encrypted envelope, if you call `captureEncrypted` | yes |
|
|
853
|
-
| Panel preferences and budget totals | yes — preferences are also mirrored to `localStorage` |
|
|
1072
|
+
| Panel preferences and budget totals | yes — preferences are also mirrored to `localStorage` under `blix:<dbName>:prefs` |
|
|
854
1073
|
| Redux actions, payloads and diffs | only if you pin the row |
|
|
855
1074
|
| Query cache rows | only if you pin the row |
|
|
856
1075
|
|
|
1076
|
+
One thing is written regardless of preserve-log: opening the database records
|
|
1077
|
+
its name in the origin-wide `localStorage` key `blix:databases`, which is how
|
|
1078
|
+
[Databases on this origin](#databases-on-this-origin) finds it in browsers
|
|
1079
|
+
without `indexedDB.databases()`. It holds database names and nothing else.
|
|
1080
|
+
|
|
857
1081
|
### What is redacted
|
|
858
1082
|
|
|
859
1083
|
Exactly four header names, and nothing else:
|
|
@@ -872,9 +1096,15 @@ people out, and `proxy-authorization`, `x-csrf-token` and
|
|
|
872
1096
|
`x-amz-security-token` are equally uncovered. If your auth travels in a header
|
|
873
1097
|
that is not one of the four above, it is captured verbatim.
|
|
874
1098
|
|
|
1099
|
+
The same four are masked whichever client made the request, and whatever form
|
|
1100
|
+
the headers were passed in: `AxiosHeaders`, a plain object, a `Headers`
|
|
1101
|
+
instance or `[name, value]` pairs.
|
|
1102
|
+
|
|
875
1103
|
Masking is partial rather than total: for a value longer than 12 characters
|
|
876
1104
|
the first 8 and last 4 survive, so you can still tell which token you sent.
|
|
877
|
-
Shorter values are replaced outright.
|
|
1105
|
+
Shorter values are replaced outright. The Headers tab tags every masked row
|
|
1106
|
+
`MASKED` rather than leaving you to infer it from an ellipsis, and the stored
|
|
1107
|
+
value keeps a `(masked)` suffix so every export path carries the fact too.
|
|
878
1108
|
|
|
879
1109
|
**Nothing inside a body is redacted.** Request bodies, response bodies, error
|
|
880
1110
|
payloads, the encrypted request/response values, Redux payloads and diffs, and
|
|
@@ -899,21 +1129,39 @@ records or 24 MB of newer traffic push it out, or when you clear it yourself.
|
|
|
899
1129
|
On a low-traffic app with preserve-log left on, a captured token stays in the
|
|
900
1130
|
browser profile indefinitely.
|
|
901
1131
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1132
|
+
Three ways to purge:
|
|
1133
|
+
|
|
1134
|
+
| Where | Note |
|
|
1135
|
+
| --- | --- |
|
|
1136
|
+
| The persisted-size label in the status bar — the one reading `12 saved · 3.4 MB` | The label *is* the control, and it is the only one that asks twice: click once to arm it, at which point it changes to `Purge saved log?`, and click again. It disarms itself after three seconds. Rendered only while preserve-log is on |
|
|
1137
|
+
| **⋯ More actions** → **Purge saved log** | Deletes on a single press, with no confirmation |
|
|
1138
|
+
| Command palette (`Ctrl/⌘ K`) → **Purge the saved log** | Deletes on a single press, with no confirmation |
|
|
1139
|
+
|
|
1140
|
+
The menu and palette entries are disabled when nothing is on disk, but unlike
|
|
1141
|
+
the status-bar label they do not depend on preserve-log being on — so a log
|
|
1142
|
+
written earlier in the session can still be deleted after you have switched
|
|
1143
|
+
the toggle off.
|
|
905
1144
|
|
|
906
1145
|
Switching preserve-log **off** also clears the stored entries, so turning it
|
|
907
1146
|
off is itself a way to drop everything Blix has written.
|
|
908
1147
|
|
|
909
|
-
|
|
1148
|
+
A purge can be **blocked**: IndexedDB will not delete a database that another
|
|
1149
|
+
tab still holds open. The panel reports that, naming the database, instead of
|
|
1150
|
+
reporting the log purged while it is still on disk. Close the other tabs
|
|
1151
|
+
running the app and purge again.
|
|
1152
|
+
|
|
1153
|
+
Other projects' databases on the same origin — and the legacy `nm-devtools`
|
|
1154
|
+
database that 0.5.x and earlier wrote — are deleted from
|
|
1155
|
+
[Databases on this origin](#databases-on-this-origin), not by Purge.
|
|
1156
|
+
|
|
1157
|
+
Every path clears the captured entries; Purge additionally deletes the
|
|
910
1158
|
IndexedDB database itself. **Your panel preferences survive either way** —
|
|
911
1159
|
they are mirrored to `localStorage`, and a fresh database is re-seeded from
|
|
912
|
-
that mirror on the next boot.
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1160
|
+
that mirror on the next boot. Both the database and its mirror key are scoped
|
|
1161
|
+
to this project's `dbName`, so a purge affects only the project that ran it and
|
|
1162
|
+
cannot restore — or destroy — another project's preferences on the same origin.
|
|
1163
|
+
There is no UI or API for clearing them, and no programmatic API for purging
|
|
1164
|
+
either.
|
|
917
1165
|
|
|
918
1166
|
### Threat model
|
|
919
1167
|
|
|
@@ -922,6 +1170,12 @@ rest. Any script running on that origin can read Blix's database — including
|
|
|
922
1170
|
browser extension content scripts with access to the origin. Whatever you
|
|
923
1171
|
capture is readable by whatever you have installed.
|
|
924
1172
|
|
|
1173
|
+
A per-project `dbName` does not change that. It keeps projects from mixing
|
|
1174
|
+
their logs; it does not isolate them. The panel itself can open another
|
|
1175
|
+
project's database on the same origin — **Peek** in
|
|
1176
|
+
[Databases on this origin](#databases-on-this-origin) lists its newest URLs,
|
|
1177
|
+
methods and statuses — and so can any other script there.
|
|
1178
|
+
|
|
925
1179
|
Export and copy move captured data out of the browser entirely:
|
|
926
1180
|
|
|
927
1181
|
| Path | Carries |
|
|
@@ -946,6 +1200,12 @@ bodies, and it is the artifact most likely to end up attached to a ticket.
|
|
|
946
1200
|
[Redux](#redux--createreduxmonitormiddlewareoptions).
|
|
947
1201
|
- **Treat an exported HAR as a credential-bearing file.** Do not attach one to
|
|
948
1202
|
a public issue, and do not commit one.
|
|
1203
|
+
- **After upgrading from 0.5.x or earlier, delete `nm-devtools`.** Blix no
|
|
1204
|
+
longer reads or writes that database, and it does not delete it for you:
|
|
1205
|
+
anything it holds stays on disk until you remove it from
|
|
1206
|
+
[Databases on this origin](#databases-on-this-origin).
|
|
1207
|
+
- **Scope `attachFetchMonitor` with `ignore`** if third-party scripts on the
|
|
1208
|
+
page send data you do not want in the log.
|
|
949
1209
|
|
|
950
1210
|
---
|
|
951
1211
|
|
|
@@ -962,9 +1222,10 @@ directive, so it stays usable from a server module — which the root entry, by
|
|
|
962
1222
|
virtue of the directive that lets `<Blix />` be rendered from a server
|
|
963
1223
|
component, is not.
|
|
964
1224
|
|
|
965
|
-
The `/capture` entry exports `attachHttpMonitor`, `
|
|
966
|
-
`
|
|
967
|
-
`withInitiatorCapture`, and the supporting types
|
|
1225
|
+
The `/capture` entry exports `attachHttpMonitor`, `attachFetchMonitor`,
|
|
1226
|
+
`captureEncrypted`, `createReduxMonitorMiddleware`, `tapQueryClient`,
|
|
1227
|
+
`tapRealtimeAdapter`, `withInitiatorCapture`, and the supporting types
|
|
1228
|
+
(`EncryptedPayload`, `FetchMonitorOptions`, `FetchIgnoreRule`,
|
|
968
1229
|
`ReduxCaptureOptions`, `RealtimeAdapterLike`, `MonitorEntry`, …).
|
|
969
1230
|
|
|
970
1231
|
---
|