@hakam-aldeen-kh/blix 0.10.0 → 0.11.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 +209 -51
- package/dist/{DevToolsPanel-KK5DTPZA.js → DevToolsPanel-M5NWMB3M.js} +1428 -698
- package/dist/capture/index.d.ts +108 -2
- package/dist/capture/index.js +2 -2
- package/dist/{chunk-IWI5YFYP.js → chunk-IWW5LH64.js} +334 -16
- package/dist/{chunk-TV2KC74D.js → chunk-P5272747.js} +301 -30
- package/dist/index.d.ts +1 -1
- package/dist/index.js +28 -7
- package/package.json +18 -7
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ no reason to carry a second build output.
|
|
|
43
43
|
|
|
44
44
|
### Peer dependencies
|
|
45
45
|
|
|
46
|
-
`react` and `react-dom` (v19) are required. `axios`, `@reduxjs/toolkit` and
|
|
46
|
+
`react` and `react-dom` (v18 or v19) are required. `axios`, `@reduxjs/toolkit` and
|
|
47
47
|
`@tanstack/react-query` are **optional** peers — you only need the ones whose
|
|
48
48
|
capture you actually use. The capture layer is structurally typed against each
|
|
49
49
|
of them and imports none of them — not at runtime, and not in its published
|
|
@@ -58,6 +58,31 @@ axios installed, not even for types.
|
|
|
58
58
|
|
|
59
59
|
---
|
|
60
60
|
|
|
61
|
+
## Upgrading to 0.11
|
|
62
|
+
|
|
63
|
+
Four things behave differently, and none needs a code change unless you read
|
|
64
|
+
entries yourself:
|
|
65
|
+
|
|
66
|
+
- **`entry.error` is an object.** A failed request stores a
|
|
67
|
+
[`CapturedError`](#entryerror--capturederror) — `kind`, `message`, `status`,
|
|
68
|
+
… — instead of the response body or a string. The body is on
|
|
69
|
+
`error.response`. Anything that read `entry.error` from an export has to
|
|
70
|
+
follow it there.
|
|
71
|
+
- **Saved logs are cleared once.** Logs written by earlier versions are
|
|
72
|
+
discarded the first time 0.11 opens the database. Panel preferences are
|
|
73
|
+
kept.
|
|
74
|
+
- **Docked panels push the page.** A panel docked to an edge reserves that side
|
|
75
|
+
with inline padding on `<html>` rather than covering the app, and no longer
|
|
76
|
+
closes on an outside click. `position: fixed` and `sticky` elements are not
|
|
77
|
+
moved by padding — opt them in with `--blix-dock-top`, `-right`, `-bottom`
|
|
78
|
+
and `-left`, e.g. `right: var(--blix-dock-right, 0px)`. See
|
|
79
|
+
[Finding your way around](#finding-your-way-around).
|
|
80
|
+
- **Shortcuts only act inside the panel.** Single-key shortcuts no longer fire
|
|
81
|
+
while focus is in your app; `Ctrl/⌘` + backtick still toggles the panel from
|
|
82
|
+
anywhere.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
61
86
|
## The module-init call-order contract
|
|
62
87
|
|
|
63
88
|
**This is the part that is easy to get wrong.** Capture functions must be
|
|
@@ -175,59 +200,56 @@ ordering is actually correct.
|
|
|
175
200
|
|
|
176
201
|
#### Failed requests and the shape of your rejection
|
|
177
202
|
|
|
178
|
-
Blix
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
203
|
+
Blix records how a request ended **at the wire**: before the first of your
|
|
204
|
+
response interceptors runs, while the value is still the `AxiosError` — or the
|
|
205
|
+
response — that axios produced. So a response interceptor that normalises
|
|
206
|
+
errors into your own domain type no longer costs you the entry:
|
|
182
207
|
|
|
183
208
|
```ts
|
|
184
|
-
//
|
|
209
|
+
// Fine since 0.11: the failure was recorded before this ran
|
|
185
210
|
instance.interceptors.response.use(undefined, (error) =>
|
|
186
211
|
Promise.reject(error.response?.data ?? fallbackError),
|
|
187
212
|
);
|
|
188
213
|
```
|
|
189
214
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
request bodies and correlated errors while also discarding the `AxiosError`.
|
|
215
|
+
(Through 0.10 this left **every non-2xx request `pending`** for the rest of the
|
|
216
|
+
session, because the value Blix received had no `config` to correlate by.)
|
|
217
|
+
|
|
218
|
+
The entry carries the real failure — status, response body, the client's code.
|
|
219
|
+
What your interceptor handed the caller *instead* is recorded beside it as
|
|
220
|
+
`error.handledAs`, whenever Blix can tie the two together: your error keeps the
|
|
221
|
+
original on `cause`, `response`, `originalError` or `error`, or still carries
|
|
222
|
+
`config`. An error with no link back to the original is simply not shown; the
|
|
223
|
+
entry is still settled correctly.
|
|
224
|
+
|
|
225
|
+
Three limits:
|
|
226
|
+
|
|
227
|
+
- **An error thrown by a request interceptor** never reaches the wire. It is
|
|
228
|
+
attributed to its request when that is the only one in the air; with several
|
|
229
|
+
it is left to the timeout watchdog below rather than guessed at.
|
|
230
|
+
- **A request with a `timeout`** that is still pending 3 s past it is settled
|
|
231
|
+
as a timeout that says Blix saw no answer — and corrected if one arrives
|
|
232
|
+
after all. **A request with no `timeout`** that never answers stays
|
|
233
|
+
`pending`: nothing times it out.
|
|
234
|
+
- The wire is observed by wrapping the first response interceptor already on
|
|
235
|
+
the instance. On an axios whose internals do not have the shape Blix expects
|
|
236
|
+
it is skipped, and the behaviour above falls back to correlating by
|
|
237
|
+
`error.config` alone.
|
|
214
238
|
|
|
215
239
|
#### Response interceptors that unwrap
|
|
216
240
|
|
|
217
|
-
The same
|
|
218
|
-
|
|
219
|
-
`(response) => response.data` is the usual one —
|
|
220
|
-
`
|
|
241
|
+
The same holds on the success path. A response interceptor registered before
|
|
242
|
+
Blix that returns something other than the response —
|
|
243
|
+
`(response) => response.data` is the usual one — used to leave the entry
|
|
244
|
+
`pending`. The response is now recorded at the wire, status and headers
|
|
245
|
+
included, before the interceptor unwraps it:
|
|
221
246
|
|
|
222
247
|
- your request is unaffected: your code receives exactly what your interceptor
|
|
223
248
|
returned. (Before 0.7.0 Blix threw at this point, and every request on the
|
|
224
249
|
instance failed.)
|
|
225
|
-
- the
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
Register the unwrapping interceptor **after** `attachHttpMonitor` — response
|
|
229
|
-
interceptors run in registration order, so Blix then sees the full response
|
|
230
|
-
first — or unwrap at the call site instead.
|
|
250
|
+
- the Response tab shows the body your code received — the unwrapped,
|
|
251
|
+
decrypted one — when Blix can recognise it as the same object, and the body
|
|
252
|
+
as it came off the wire otherwise.
|
|
231
253
|
|
|
232
254
|
#### Which request headers the panel shows
|
|
233
255
|
|
|
@@ -585,8 +607,72 @@ logged exactly once. The order of the two calls does not matter.
|
|
|
585
607
|
request.
|
|
586
608
|
- **No `captureEncrypted`** — see
|
|
587
609
|
[Encrypted payloads](#encrypted-payloads--captureencryptedconfig-payload).
|
|
588
|
-
- **No timeout.** A
|
|
589
|
-
|
|
610
|
+
- **No timeout watchdog.** A `fetch` that never answers stays pending until
|
|
611
|
+
the page aborts it: a `fetch` has no timeout of its own for Blix to measure
|
|
612
|
+
against. (An `AbortSignal.timeout()` that fires is recorded, as a timeout.)
|
|
613
|
+
axios differs: a request with a `timeout` is settled 3 s past it — see
|
|
614
|
+
[Failed requests](#failed-requests-and-the-shape-of-your-rejection).
|
|
615
|
+
|
|
616
|
+
### What a request records — `entry.error` and `entry.wire`
|
|
617
|
+
|
|
618
|
+
Two fields of a captured entry are worth knowing by shape, because they are
|
|
619
|
+
what the JSON and NDJSON exports carry and what the `MonitorEntry` type
|
|
620
|
+
describes. Both are the same whichever client made the request.
|
|
621
|
+
|
|
622
|
+
#### `entry.error` — `CapturedError`
|
|
623
|
+
|
|
624
|
+
A failed request stores a description of the failure, not the thrown object
|
|
625
|
+
and not just the response body:
|
|
626
|
+
|
|
627
|
+
| Field | |
|
|
628
|
+
| --- | --- |
|
|
629
|
+
| `kind` | How it failed — see below. Always present. |
|
|
630
|
+
| `message` | The error's message. Always present. |
|
|
631
|
+
| `name`, `code` | The error's class (`AxiosError`, `TypeError`) and the client's code (`ERR_NETWORK`, `ECONNABORTED`). |
|
|
632
|
+
| `status`, `statusText` | When the server answered. |
|
|
633
|
+
| `response` | **The response body**, when there was one. |
|
|
634
|
+
| `timeoutMs` | The timeout the request ran under, when that is what ended it. |
|
|
635
|
+
| `cause` | What the error says caused it — the same fields, without `kind`. |
|
|
636
|
+
| `stack` | The top frames of the error's stack. |
|
|
637
|
+
| `handledAs` | What your interceptor rejected with instead, when it replaced the client's error — the same fields, without `kind`. |
|
|
638
|
+
|
|
639
|
+
| `kind` | Meaning |
|
|
640
|
+
| --- | --- |
|
|
641
|
+
| `http` | The server answered with a status the client treats as an error. |
|
|
642
|
+
| `timeout` | No answer inside the configured time. |
|
|
643
|
+
| `network` | No answer at all: offline, DNS, refused, or blocked by CORS — the browser does not tell scripts which. |
|
|
644
|
+
| `aborted` | Cancelled by the page: `AbortController`, a cancel token, navigation. |
|
|
645
|
+
| `setup` | Thrown before the request left — by an interceptor, or while building it. |
|
|
646
|
+
| `unknown` | None of the above could be established. |
|
|
647
|
+
|
|
648
|
+
Query and mutation rows, and a Redux action whose payload is an error, store
|
|
649
|
+
the same shape. Only named fields are ever read from an error: never its
|
|
650
|
+
request config or headers.
|
|
651
|
+
|
|
652
|
+
#### `entry.wire` — `WireTiming`
|
|
653
|
+
|
|
654
|
+
Everything else Blix times is two clock reads in JavaScript. `wire` is what the
|
|
655
|
+
*browser* measured for the same request — its `PerformanceResourceTiming`
|
|
656
|
+
entry — attached automatically by every `attach*Monitor`, and shown in the
|
|
657
|
+
Timing tab under **On the wire** and **Server-Timing**:
|
|
658
|
+
|
|
659
|
+
| Field | |
|
|
660
|
+
| --- | --- |
|
|
661
|
+
| `totalMs` | The browser's total for the request. Always present. |
|
|
662
|
+
| `queueMs`, `dnsMs`, `connectMs`, `tlsMs` | Waiting for a connection, DNS, TCP, TLS. |
|
|
663
|
+
| `waitMs`, `downloadMs` | Request sent to first byte (TTFB), and first byte to last. |
|
|
664
|
+
| `redirectMs` | Time spent in redirects, when there were any. |
|
|
665
|
+
| `transferSize`, `encodedBodySize`, `decodedBodySize` | Bytes over the wire, the body as sent, and the body decoded. |
|
|
666
|
+
| `fromCache` | Nothing crossed the wire, yet there was a body. |
|
|
667
|
+
| `protocol` | `h2`, `http/1.1`, … |
|
|
668
|
+
| `serverTiming` | The response's `Server-Timing` metrics: `name`, `durationMs`, `description`. |
|
|
669
|
+
| `restricted` | See below. |
|
|
670
|
+
|
|
671
|
+
For a **cross-origin** request the browser withholds the phases and the sizes
|
|
672
|
+
unless the response carries `Timing-Allow-Origin`. Such an entry has
|
|
673
|
+
`restricted: true` and `totalMs` only — add the header on the API to get the
|
|
674
|
+
rest. An entry has no `wire` at all when the browser reported no timing for
|
|
675
|
+
it, or when Blix could not match one to the request.
|
|
590
676
|
|
|
591
677
|
### Redux — `createReduxMonitorMiddleware(options?)`
|
|
592
678
|
|
|
@@ -816,8 +902,56 @@ Query, each with its rail icon — and, in red beside a count, how many of that
|
|
|
816
902
|
source's entries failed. The dot is capture state: green while capturing,
|
|
817
903
|
amber while paused, pulsing while something is in flight.
|
|
818
904
|
|
|
819
|
-
The panel opens **floating** the first time.
|
|
820
|
-
|
|
905
|
+
The panel opens **floating** the first time. From the header it can be docked
|
|
906
|
+
to any edge instead; the choice, and each dock's size, is saved per project.
|
|
907
|
+
|
|
908
|
+
| Mode | Layout | A press outside the panel |
|
|
909
|
+
| --- | --- | --- |
|
|
910
|
+
| Float | Overlays the page; drag it by the header, resize from the corner | Closes it |
|
|
911
|
+
| Bottom, right, top, left | Takes that side of the screen — the page is pushed over, not covered | Leaves it open |
|
|
912
|
+
|
|
913
|
+
A docked panel is there so the app can be used while the log is read, so it
|
|
914
|
+
stays put; a floating one is in the way of whatever is under it, so a press
|
|
915
|
+
elsewhere dismisses it. In every mode a press outside still closes an open
|
|
916
|
+
menu or the command palette.
|
|
917
|
+
|
|
918
|
+
**Docked panels reserve their side with inline padding on `<html>`**, removed
|
|
919
|
+
again — and whatever inline value was there before restored — when the panel
|
|
920
|
+
closes, floats, moves to another side or unmounts. The same numbers are
|
|
921
|
+
published as four custom properties on `<html>`:
|
|
922
|
+
|
|
923
|
+
| Property | Value |
|
|
924
|
+
| --- | --- |
|
|
925
|
+
| `--blix-dock-top`, `--blix-dock-right`, `--blix-dock-bottom`, `--blix-dock-left` | The docked panel's size on that side, in px; `0px` on the other three. Set only while a panel is docked — absent while it is closed or floating, so always read them with a fallback |
|
|
926
|
+
|
|
927
|
+
Padding moves what is in the page's flow. An element with `position: fixed`
|
|
928
|
+
or `sticky` is positioned against the viewport, so it is **not** pushed
|
|
929
|
+
automatically — a fixed header still spans the full width, and ends up under a
|
|
930
|
+
side dock. Opt those elements in with the variables:
|
|
931
|
+
|
|
932
|
+
```css
|
|
933
|
+
.app-header { left: var(--blix-dock-left, 0px); right: var(--blix-dock-right, 0px);
|
|
934
|
+
top: var(--blix-dock-top, 0px); }
|
|
935
|
+
.chat-launcher { bottom: calc(16px + var(--blix-dock-bottom, 0px)); }
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
The fallback matters: the properties exist only while `<Blix />` is mounted,
|
|
939
|
+
which in production it is not. The same goes for a layout sized with `100vh`
|
|
940
|
+
or `100vw` — those ignore padding; `100%` of a padded `<html>` does not.
|
|
941
|
+
|
|
942
|
+
**Shortcuts belong to the panel.** Single-key shortcuts — `j`/`k`, `r`,
|
|
943
|
+
`⇧C`, Space and the rest — act only while focus is inside the panel, which it
|
|
944
|
+
is as soon as you open it or click anywhere in it. Typing in your app, in an
|
|
945
|
+
input, a `<select>`, a rich-text editor or through an IME, never triggers
|
|
946
|
+
one. Combinations with Ctrl, ⌘ or Alt are left to the browser and to your app,
|
|
947
|
+
with two exceptions: Ctrl/⌘ + backtick opens and closes the panel from anywhere
|
|
948
|
+
on the page, and `Ctrl/⌘ K` opens the palette from inside it. Shortcuts
|
|
949
|
+
follow the physical key on non-Latin layouts such as Arabic — the toggle and
|
|
950
|
+
`Ctrl/⌘ K` included — so the same key works without switching layout.
|
|
951
|
+
`Esc` closes the innermost open thing — palette, menu, then the panel — and
|
|
952
|
+
only when focus is in the panel; an Escape meant for a dialog in your app is
|
|
953
|
+
not Blix's. `r` re-sends a read at once and asks for a second press before
|
|
954
|
+
re-sending a write, exactly as the Replay button does.
|
|
821
955
|
|
|
822
956
|
**`Ctrl/⌘ K` opens the command palette**, and for several things it is the only
|
|
823
957
|
way in — sort order, row density, dock position, the copy formats, the filter
|
|
@@ -869,7 +1003,9 @@ frames, the encrypted envelope, Redux actions, and both Redux state views:
|
|
|
869
1003
|
|
|
870
1004
|
Right-click any request for **Copy as cURL** or **Copy as fetch** — the latter
|
|
871
1005
|
pastes straight into the browser console, with the body as an editable object
|
|
872
|
-
literal rather than a pre-serialized string.
|
|
1006
|
+
literal rather than a pre-serialized string. The cURL snippet is quoted for
|
|
1007
|
+
POSIX shells (bash, zsh, Git Bash); it is **not** safe to paste into Windows
|
|
1008
|
+
`cmd` or PowerShell, which quote differently.
|
|
873
1009
|
|
|
874
1010
|
### Exporting the log
|
|
875
1011
|
|
|
@@ -1043,6 +1179,11 @@ its first client render matches the server's HTML; the panel mounts on the
|
|
|
1043
1179
|
render right after. (Before 0.9.0 it branched on `typeof window` instead,
|
|
1044
1180
|
which Next.js reported as a hydration mismatch on every load.)
|
|
1045
1181
|
|
|
1182
|
+
**If the panel disappears in development**, check the console for
|
|
1183
|
+
`[blix] the panel crashed`. Blix renders inside an error boundary of its own,
|
|
1184
|
+
so a failure in the panel removes the panel until the next reload and leaves
|
|
1185
|
+
your app running — the logged error is the thing to report.
|
|
1186
|
+
|
|
1046
1187
|
### Enabling Blix in a production build (staging)
|
|
1047
1188
|
|
|
1048
1189
|
A staging or test server usually runs a production build, where the panel is
|
|
@@ -1088,6 +1229,11 @@ The variable is read under its `NEXT_PUBLIC_` name, which is a Next.js
|
|
|
1088
1229
|
convention. The opt-in has been verified with Next.js 16 (Turbopack) only;
|
|
1089
1230
|
other bundlers do not inline that name by default.
|
|
1090
1231
|
|
|
1232
|
+
So `NEXT_PUBLIC_BLIX` is honoured only where the bundler inlines it. On Vite,
|
|
1233
|
+
esbuild, Rollup and the like it is ignored, safely: the read is guarded, so it
|
|
1234
|
+
does not depend on a `process` global existing in the browser, and Blix stays
|
|
1235
|
+
off in production.
|
|
1236
|
+
|
|
1091
1237
|
### Guard your call sites
|
|
1092
1238
|
|
|
1093
1239
|
**No-op is not the same as eliminated.** The argument above works for the
|
|
@@ -1232,6 +1378,18 @@ Shorter values are replaced outright. The Headers tab tags every masked row
|
|
|
1232
1378
|
`MASKED` rather than leaving you to infer it from an ellipsis, and the stored
|
|
1233
1379
|
value keeps a `(masked)` suffix so every export path carries the fact too.
|
|
1234
1380
|
|
|
1381
|
+
**Errors are described, never copied.** A failed TanStack query or mutation,
|
|
1382
|
+
and a Redux action whose payload is an error, are stored as a described error
|
|
1383
|
+
— kind, name, code, message, status, the response body, cause and stack —
|
|
1384
|
+
and never as the error object itself. An `AxiosError` carries the request's
|
|
1385
|
+
config and headers as ordinary properties; none of that is read.
|
|
1386
|
+
|
|
1387
|
+
**The four names are also masked as keys, at any depth, in data Blix copies**:
|
|
1388
|
+
Redux payloads and state diffs, query and mutation data, and everything
|
|
1389
|
+
written to IndexedDB. An HTTP body is still shown as captured while it is
|
|
1390
|
+
live in the panel — a key named `authorization` inside one is masked only in
|
|
1391
|
+
the saved copy.
|
|
1392
|
+
|
|
1235
1393
|
#### Showing `Authorization` in full
|
|
1236
1394
|
|
|
1237
1395
|
Blix is development-only, and a developer reading their own token on their own
|
|
@@ -1280,12 +1438,12 @@ as an unsigned token. The claims
|
|
|
1280
1438
|
are part of the entry, so — unlike the token — they are saved with
|
|
1281
1439
|
preserve-log on and included in the JSON and NDJSON exports.
|
|
1282
1440
|
|
|
1283
|
-
**
|
|
1284
|
-
|
|
1285
|
-
realtime frames
|
|
1286
|
-
values and
|
|
1287
|
-
`refreshToken` field is captured, and with preserve-log
|
|
1288
|
-
exactly as it appears. Blix has no mechanism to do otherwise: it has no schema
|
|
1441
|
+
**Beyond those four key names, nothing inside a body is redacted.** Request
|
|
1442
|
+
bodies, response bodies, error payloads, the encrypted request/response
|
|
1443
|
+
values, Redux payloads and diffs, and realtime frames pass through a walker
|
|
1444
|
+
that truncates large values and looks for those four names only. A
|
|
1445
|
+
`password`, `ssn` or `refreshToken` field is captured, and with preserve-log
|
|
1446
|
+
on written to disk, exactly as it appears. Blix has no mechanism to do otherwise: it has no schema
|
|
1289
1447
|
for your payloads and no way to tell a secret from any other string.
|
|
1290
1448
|
|
|
1291
1449
|
### Retention and clearing
|