@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.
@@ -21,15 +21,37 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
21
21
  // src/capture/monitorConfig.ts
22
22
  var MONITOR_ENABLED = process.env.NODE_ENV === "development" && typeof window !== "undefined";
23
23
  var NM_SCHEMA_VERSION = 1;
24
- var NM_DB_NAME = "nm-devtools";
24
+ var DB_PREFIX = "blix:";
25
+ var LEGACY_DB_NAME = "nm-devtools";
26
+ var NM_DB_NAME = `${DB_PREFIX}default`;
25
27
  var NM_DB_VERSION = 1;
28
+ function resolveDbName(name) {
29
+ if (!name) return NM_DB_NAME;
30
+ return name.startsWith(DB_PREFIX) ? name : `${DB_PREFIX}${name}`;
31
+ }
26
32
  var _activeDbName = NM_DB_NAME;
33
+ var _dbOpened = false;
34
+ function markDbOpened() {
35
+ _dbOpened = true;
36
+ }
27
37
  function configureDbName(name) {
28
- _activeDbName = name;
38
+ const resolved = resolveDbName(name);
39
+ if (_dbOpened && resolved !== _activeDbName) {
40
+ if (process.env.NODE_ENV === "development") {
41
+ console.warn(
42
+ `[blix] configureDbName(${JSON.stringify(name)}) had no effect \u2014 the database "${_activeDbName}" is already open, and Blix does not switch databases at runtime. Requested "${resolved}". Set dbName before the panel mounts: either <Blix dbName="\u2026" /> or attachHttpMonitor(client, { dbName: "\u2026" }).`
43
+ );
44
+ }
45
+ return;
46
+ }
47
+ _activeDbName = resolved;
29
48
  }
30
49
  function getActiveDbName() {
31
50
  return _activeDbName;
32
51
  }
52
+ function isSharedDefaultDb() {
53
+ return _activeDbName === NM_DB_NAME;
54
+ }
33
55
  var POOL_OF = {
34
56
  http: "net",
35
57
  ws: "net",
@@ -54,7 +76,6 @@ var PERSIST_MAX_ENTRY_BYTES = 512 * 1024;
54
76
  var STRING_CAP = 32 * 1024;
55
77
  var ARRAY_CAP = 200;
56
78
  var SEARCH_INDEX_CAP = 64 * 1024;
57
- var PENDING_WINDOW_CAP_MS = 3e4;
58
79
  var TIME_ORIGIN = typeof performance !== "undefined" && typeof performance.timeOrigin === "number" ? performance.timeOrigin : Date.now();
59
80
  var PAGE_LOAD_AT = Date.now();
60
81
  function makeLoadId() {
@@ -98,25 +119,81 @@ var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
98
119
  "set-cookie",
99
120
  "x-api-key"
100
121
  ]);
122
+ var MASK_SUFFIX = " (masked)";
101
123
  function maskHeaderValue(key, value) {
102
124
  if (!SENSITIVE_HEADERS.has(key.toLowerCase())) return value;
103
125
  if (value.length <= 12) return "\u2022\u2022\u2022\u2022";
104
- return `${value.slice(0, 8)}\u2026${value.slice(-4)} (masked)`;
126
+ return `${value.slice(0, 8)}\u2026${value.slice(-4)}${MASK_SUFFIX}`;
127
+ }
128
+ function isHeadersLike(value) {
129
+ if (typeof Headers !== "undefined" && value instanceof Headers) return true;
130
+ const candidate = value;
131
+ return typeof candidate.forEach === "function" && typeof candidate.get === "function" && typeof candidate.toJSON !== "function";
132
+ }
133
+ function flattenHeaders(headers) {
134
+ const out = {};
135
+ if (!headers || typeof headers !== "object") return out;
136
+ const spelling = /* @__PURE__ */ new Map();
137
+ const add = (rawName, rawValue) => {
138
+ if (rawValue == null) return;
139
+ const name = String(rawName);
140
+ const lower = name.toLowerCase();
141
+ const separator = lower === "set-cookie" ? "\n" : ", ";
142
+ const value = Array.isArray(rawValue) ? rawValue.map(String).join(separator) : String(rawValue);
143
+ const existing = spelling.get(lower);
144
+ if (existing === void 0) {
145
+ spelling.set(lower, name);
146
+ out[name] = value;
147
+ } else {
148
+ out[existing] = `${out[existing]}${separator}${value}`;
149
+ }
150
+ };
151
+ try {
152
+ if (Array.isArray(headers)) {
153
+ for (const pair of headers) {
154
+ if (Array.isArray(pair) && pair.length >= 2) add(pair[0], pair[1]);
155
+ }
156
+ return out;
157
+ }
158
+ if (isHeadersLike(headers)) {
159
+ headers.forEach((value, name) => add(name, value));
160
+ return out;
161
+ }
162
+ for (const [name, value] of Object.entries(headers)) {
163
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) continue;
164
+ add(name, value);
165
+ }
166
+ } catch (e) {
167
+ }
168
+ return out;
105
169
  }
106
170
  function serializeHeaders(headers) {
107
171
  const out = {};
108
172
  if (!headers || typeof headers !== "object") return out;
109
- const source = typeof headers.toJSON === "function" ? headers.toJSON() : headers;
110
- if (!source || typeof source !== "object") return out;
111
- for (const [key, value] of Object.entries(source)) {
112
- if (value == null) continue;
113
- if (typeof value === "object" && !Array.isArray(value)) continue;
114
- const str = Array.isArray(value) ? value.join(", ") : String(value);
115
- out[key] = maskHeaderValue(key, str);
173
+ try {
174
+ const source = typeof headers.toJSON === "function" ? headers.toJSON() : headers;
175
+ for (const [key, value] of Object.entries(flattenHeaders(source))) {
176
+ out[key] = maskHeaderValue(key, value);
177
+ }
178
+ } catch (e) {
116
179
  }
117
180
  return out;
118
181
  }
119
182
  var ESTIMATE_CEILING = 8 * 1024 * 1024;
183
+ function knownByteLength(node) {
184
+ if (typeof Blob !== "undefined" && node instanceof Blob) return node.size;
185
+ if (typeof ArrayBuffer !== "undefined") {
186
+ if (node instanceof ArrayBuffer) return node.byteLength;
187
+ if (ArrayBuffer.isView(node)) return node.byteLength;
188
+ }
189
+ if (typeof SharedArrayBuffer !== "undefined" && node instanceof SharedArrayBuffer) {
190
+ return node.byteLength;
191
+ }
192
+ if (typeof URLSearchParams !== "undefined" && node instanceof URLSearchParams) {
193
+ return node.toString().length;
194
+ }
195
+ return null;
196
+ }
120
197
  function estimateBytes(value) {
121
198
  let total = 0;
122
199
  const seen = /* @__PURE__ */ new WeakSet();
@@ -141,6 +218,11 @@ function estimateBytes(value) {
141
218
  return;
142
219
  }
143
220
  if (typeof node !== "object") return;
221
+ const known = knownByteLength(node);
222
+ if (known !== null) {
223
+ total += known;
224
+ return;
225
+ }
144
226
  if (seen.has(node)) {
145
227
  total += 8;
146
228
  return;
@@ -411,6 +493,103 @@ var NetworkMonitor = class {
411
493
  };
412
494
  var networkMonitor = new NetworkMonitor();
413
495
 
496
+ // src/capture/monitorInitiator.ts
497
+ var NOISE = [
498
+ /[\\/]node_modules[\\/]axios[\\/]/,
499
+ /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
500
+ /[\\/]node_modules[\\/]@tanstack[\\/]/,
501
+ /monitorInitiator|networkMonitor|monitorPersistence/,
502
+ /[\\/]core[\\/]network[\\/]axios\.ts/,
503
+ /node:internal|next[\\/]dist/,
504
+ /^\s*at (?:Object\.)?(?:Promise|process|async)\b/
505
+ ];
506
+ var FRAME_RE = /^\s*at (?:(.+?) \()?(.+?):(\d+):(\d+)\)?$/;
507
+ function tidyPath(file) {
508
+ const cleaned = file.replace(/^webpack-internal:\/{3}\(.*?\)\/\.?/, "").replace(/^rsc:\/{3}/, "").replace(/\?.*$/, "");
509
+ const at = cleaned.lastIndexOf("/src/");
510
+ if (at >= 0) return cleaned.slice(at + 1);
511
+ const backslash = cleaned.lastIndexOf("\\src\\");
512
+ if (backslash >= 0) return cleaned.slice(backslash + 1).replace(/\\/g, "/");
513
+ return cleaned;
514
+ }
515
+ var MAX_FRAMES = 8;
516
+ function parseStack(stack, skipFrames = 0) {
517
+ if (!stack) return [];
518
+ const out = [];
519
+ for (const line of stack.split("\n").slice(1 + skipFrames)) {
520
+ if (NOISE.some((re) => re.test(line))) continue;
521
+ const match = FRAME_RE.exec(line);
522
+ if (!match) continue;
523
+ const [, fn, file, lineNo, col] = match;
524
+ out.push({
525
+ fn: (fn == null ? void 0 : fn.trim()) || void 0,
526
+ file: tidyPath(file),
527
+ line: Number(lineNo),
528
+ col: Number(col)
529
+ });
530
+ if (out.length >= MAX_FRAMES) break;
531
+ }
532
+ return out;
533
+ }
534
+ function summarizeInitiator(frames) {
535
+ var _a, _b;
536
+ if (frames.length === 0) return "";
537
+ const own = (_a = frames.find((f) => /^src\/(features|shared|app)\//.test(f.file))) != null ? _a : frames[0];
538
+ const name = own.fn ? `${own.fn} \xB7 ` : "";
539
+ const file = (_b = own.file.split("/").pop()) != null ? _b : own.file;
540
+ return `${name}${file}${own.line ? `:${own.line}` : ""}`;
541
+ }
542
+ var captureEnabled = true;
543
+ function setInitiatorCapture(on) {
544
+ captureEnabled = on;
545
+ }
546
+ function captureFrames(skip = 0) {
547
+ if (!captureEnabled) return void 0;
548
+ const previousLimit = Error.stackTraceLimit;
549
+ try {
550
+ Error.stackTraceLimit = 24 + 1 + skip;
551
+ const stack = new Error().stack;
552
+ return parseStack(stack, 1 + skip);
553
+ } catch (e) {
554
+ return void 0;
555
+ } finally {
556
+ Error.stackTraceLimit = previousLimit;
557
+ }
558
+ }
559
+ function withInitiatorCapture(instance) {
560
+ if (!MONITOR_ENABLED) return instance;
561
+ const attach = (config) => {
562
+ if (!config) return config;
563
+ if (!config.__monitorInitiator) config.__monitorInitiator = captureFrames(2);
564
+ return config;
565
+ };
566
+ const proxy = new Proxy(instance, {
567
+ apply(target, thisArg, args) {
568
+ const [config] = args;
569
+ return Reflect.apply(
570
+ target,
571
+ thisArg,
572
+ [attach(config)]
573
+ );
574
+ },
575
+ get(target, prop, receiver) {
576
+ const value = Reflect.get(target, prop, receiver);
577
+ if (typeof value !== "function") return value;
578
+ if (prop === "request") {
579
+ return (config) => value.call(target, attach(config));
580
+ }
581
+ if (prop === "post" || prop === "put" || prop === "patch") {
582
+ return (url, data, config) => value.call(target, url, data, attach(config != null ? config : {}));
583
+ }
584
+ if (prop === "get" || prop === "delete" || prop === "head") {
585
+ return (url, config) => value.call(target, url, attach(config != null ? config : {}));
586
+ }
587
+ return value.bind(target);
588
+ }
589
+ });
590
+ return proxy;
591
+ }
592
+
414
593
  // src/capture/monitorTruncate.ts
415
594
  function truncateForPersist(value, maxBytes) {
416
595
  let used = 0;
@@ -940,128 +1119,36 @@ function tapQueryClient(client) {
940
1119
  };
941
1120
  }
942
1121
 
943
- // src/capture/monitorInitiator.ts
944
- var NOISE = [
945
- /[\\/]node_modules[\\/]axios[\\/]/,
946
- /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
947
- /[\\/]node_modules[\\/]@tanstack[\\/]/,
948
- /monitorInitiator|networkMonitor|monitorPersistence/,
949
- /[\\/]core[\\/]network[\\/]axios\.ts/,
950
- /node:internal|next[\\/]dist/,
951
- /^\s*at (?:Object\.)?(?:Promise|process|async)\b/
952
- ];
953
- var FRAME_RE = /^\s*at (?:(.+?) \()?(.+?):(\d+):(\d+)\)?$/;
954
- function tidyPath(file) {
955
- const cleaned = file.replace(/^webpack-internal:\/{3}\(.*?\)\/\.?/, "").replace(/^rsc:\/{3}/, "").replace(/\?.*$/, "");
956
- const at = cleaned.lastIndexOf("/src/");
957
- if (at >= 0) return cleaned.slice(at + 1);
958
- const backslash = cleaned.lastIndexOf("\\src\\");
959
- if (backslash >= 0) return cleaned.slice(backslash + 1).replace(/\\/g, "/");
960
- return cleaned;
961
- }
962
- var MAX_FRAMES = 8;
963
- function parseStack(stack) {
964
- if (!stack) return [];
965
- const out = [];
966
- for (const line of stack.split("\n").slice(1)) {
967
- if (NOISE.some((re) => re.test(line))) continue;
968
- const match = FRAME_RE.exec(line);
969
- if (!match) continue;
970
- const [, fn, file, lineNo, col] = match;
971
- out.push({
972
- fn: (fn == null ? void 0 : fn.trim()) || void 0,
973
- file: tidyPath(file),
974
- line: Number(lineNo),
975
- col: Number(col)
976
- });
977
- if (out.length >= MAX_FRAMES) break;
978
- }
979
- return out;
980
- }
981
- function summarizeInitiator(frames) {
982
- var _a, _b;
983
- if (frames.length === 0) return "";
984
- const own = (_a = frames.find((f) => /^src\/(features|shared|app)\//.test(f.file))) != null ? _a : frames[0];
985
- const name = own.fn ? `${own.fn} \xB7 ` : "";
986
- const file = (_b = own.file.split("/").pop()) != null ? _b : own.file;
987
- return `${name}${file}${own.line ? `:${own.line}` : ""}`;
988
- }
989
- var captureEnabled = true;
990
- function setInitiatorCapture(on) {
991
- captureEnabled = on;
992
- }
993
- function captureFrames() {
994
- if (!captureEnabled) return void 0;
995
- const previousLimit = Error.stackTraceLimit;
996
- try {
997
- Error.stackTraceLimit = 24;
998
- const stack = new Error().stack;
999
- return parseStack(stack);
1000
- } catch (e) {
1001
- return void 0;
1002
- } finally {
1003
- Error.stackTraceLimit = previousLimit;
1004
- }
1005
- }
1006
- function withInitiatorCapture(instance) {
1007
- if (!MONITOR_ENABLED) return instance;
1008
- const attach = (config) => {
1009
- if (!config) return config;
1010
- if (!config.__monitorInitiator) config.__monitorInitiator = captureFrames();
1011
- return config;
1012
- };
1013
- const proxy = new Proxy(instance, {
1014
- apply(target, thisArg, args) {
1015
- const [config] = args;
1016
- return Reflect.apply(
1017
- target,
1018
- thisArg,
1019
- [attach(config)]
1020
- );
1021
- },
1022
- get(target, prop, receiver) {
1023
- const value = Reflect.get(target, prop, receiver);
1024
- if (typeof value !== "function") return value;
1025
- if (prop === "request") {
1026
- return (config) => value.call(target, attach(config));
1027
- }
1028
- if (prop === "post" || prop === "put" || prop === "patch") {
1029
- return (url, data, config) => value.call(
1030
- target,
1031
- url,
1032
- data,
1033
- attach(config != null ? config : {})
1034
- );
1035
- }
1036
- if (prop === "get" || prop === "delete" || prop === "head") {
1037
- return (url, config) => value.call(target, url, attach(config != null ? config : {}));
1038
- }
1039
- return value.bind(target);
1040
- }
1041
- });
1042
- return proxy;
1043
- }
1044
-
1045
1122
  export {
1046
1123
  __spreadValues,
1047
1124
  __spreadProps,
1048
1125
  MONITOR_ENABLED,
1049
1126
  NM_SCHEMA_VERSION,
1127
+ DB_PREFIX,
1128
+ LEGACY_DB_NAME,
1050
1129
  NM_DB_VERSION,
1130
+ markDbOpened,
1051
1131
  configureDbName,
1052
1132
  getActiveDbName,
1133
+ isSharedDefaultDb,
1053
1134
  MAX_ENTRIES,
1054
1135
  PERSIST_MAX_ENTRIES,
1055
1136
  PERSIST_MAX_BYTES,
1056
1137
  PERSIST_MAX_ENTRY_BYTES,
1057
1138
  SEARCH_INDEX_CAP,
1058
- PENDING_WINDOW_CAP_MS,
1059
1139
  now,
1060
1140
  getOwner,
1061
1141
  serializeBody,
1142
+ SENSITIVE_HEADERS,
1143
+ MASK_SUFFIX,
1144
+ flattenHeaders,
1062
1145
  serializeHeaders,
1063
1146
  estimateBytes,
1064
1147
  networkMonitor,
1148
+ summarizeInitiator,
1149
+ setInitiatorCapture,
1150
+ captureFrames,
1151
+ withInitiatorCapture,
1065
1152
  truncateForPersist,
1066
1153
  isTruncationMarker,
1067
1154
  setReduxIgnoreList,
@@ -1069,8 +1156,5 @@ export {
1069
1156
  markReplaying,
1070
1157
  createReduxMonitorMiddleware,
1071
1158
  getTappedQueryClient,
1072
- tapQueryClient,
1073
- summarizeInitiator,
1074
- setInitiatorCapture,
1075
- withInitiatorCapture
1159
+ tapQueryClient
1076
1160
  };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
- export { CacheLike, Corner, DiffOp, DockMode, EncryptedPayload, FrameDirection, InitiatorFrame, MiddlewareApiLike, MiddlewareLike, MonitorEntry, MonitorKind, MonitorPrefs, MonitorState, PersistedEntry, Pos, QueryClientLike, QueryMeta, RealtimeAdapterLike, ReduxActionMeta, ReduxCaptureOptions, Size, StateDiff, StateDiffEntry, TimingMarks, WsFrame, attachHttpMonitor, captureEncrypted, createReduxMonitorMiddleware, tapQueryClient, tapRealtimeAdapter, withInitiatorCapture } from './capture/index.js';
1
+ export { CacheLike, Corner, DiffOp, DockMode, EncryptedPayload, FetchIgnoreRule, FetchMonitorOptions, FrameDirection, InitiatorFrame, MiddlewareApiLike, MiddlewareLike, MonitorEntry, MonitorKind, MonitorPrefs, MonitorState, PersistedEntry, Pos, QueryClientLike, QueryMeta, RealtimeAdapterLike, ReduxActionMeta, ReduxCaptureOptions, Size, StateDiff, StateDiffEntry, TimingMarks, WsFrame, attachFetchMonitor, attachHttpMonitor, captureEncrypted, createReduxMonitorMiddleware, tapQueryClient, tapRealtimeAdapter, withInitiatorCapture } from './capture/index.js';
2
2
  import * as react from 'react';
3
- import 'axios';
4
3
 
5
4
  /** Structural redux-store shape — only the subset the panel actually uses.
6
5
  * Mirrors `MiddlewareApiLike` in `reduxCapture.ts` but adds `subscribe` and
@@ -66,8 +65,9 @@ interface HttpClientLike {
66
65
  interface BlixProps {
67
66
  store?: StoreLike<unknown>;
68
67
  apiClient?: HttpClientLike;
69
- /** IndexedDB database name. Defaults to "nm-devtools". Useful when running
70
- * multiple apps on the same origin that would otherwise share a log. */
68
+ /** IndexedDB database name, prefixed to `blix:<dbName>`. Defaults to
69
+ * `blix:default`. Useful when running multiple apps on the same origin that
70
+ * would otherwise share a log. */
71
71
  dbName?: string;
72
72
  }
73
73
  declare function Blix({ store, apiClient, dbName }: BlixProps): react.JSX.Element | null;
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  "use client";
2
2
  import {
3
+ attachFetchMonitor,
3
4
  attachHttpMonitor,
4
5
  captureEncrypted,
5
6
  tapRealtimeAdapter
6
- } from "./chunk-OQHKPRNI.js";
7
+ } from "./chunk-4JZEVUOR.js";
7
8
  import {
8
9
  BlixContext
9
10
  } from "./chunk-PQIOIMAQ.js";
@@ -12,16 +13,23 @@ import {
12
13
  createReduxMonitorMiddleware,
13
14
  tapQueryClient,
14
15
  withInitiatorCapture
15
- } from "./chunk-MRTSIXR7.js";
16
+ } from "./chunk-FIYP47QZ.js";
16
17
 
17
18
  // src/ui/Blix.tsx
18
- import { lazy, Suspense, useRef } from "react";
19
+ import { lazy, Suspense, useEffect, useRef } from "react";
19
20
  import { jsx } from "react/jsx-runtime";
20
21
  function Blix({ store, apiClient, dbName }) {
21
22
  const panelRef = useRef(null);
23
+ useEffect(() => {
24
+ if (process.env.NODE_ENV !== "development") return;
25
+ if (dbName) return;
26
+ console.warn(
27
+ '[blix] No dbName prop \u2014 using the shared database "blix:default".\nIndexedDB is scoped to the origin, not to your app, so every app served from this origin writes to that one database and entries from other projects will appear in this panel.\nFix: <Blix dbName="my-app" />\nExisting databases on this origin are listed under \u201CDatabases on this origin\u201D in the command palette (Ctrl/\u2318 K).'
28
+ );
29
+ }, []);
22
30
  if (typeof window !== "undefined" && process.env.NODE_ENV === "development") {
23
31
  if (!panelRef.current) {
24
- panelRef.current = lazy(() => import("./DevToolsPanel-TXMT43YN.js"));
32
+ panelRef.current = lazy(() => import("./DevToolsPanel-QTVFBNU5.js"));
25
33
  }
26
34
  const Panel = panelRef.current;
27
35
  if (dbName) configureDbName(dbName);
@@ -31,6 +39,7 @@ function Blix({ store, apiClient, dbName }) {
31
39
  }
32
40
  export {
33
41
  Blix,
42
+ attachFetchMonitor,
34
43
  attachHttpMonitor,
35
44
  captureEncrypted,
36
45
  createReduxMonitorMiddleware,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hakam-aldeen-kh/blix",
3
- "version": "0.4.2",
3
+ "version": "0.6.0",
4
4
  "description": "Dev-tools panel for React apps — HTTP, Redux, Query and Realtime monitoring.",
5
5
  "keywords": [
6
6
  "devtools",
@@ -90,6 +90,7 @@
90
90
  "@types/node": "^20.0.0",
91
91
  "@types/react": "^19.0.0",
92
92
  "@types/react-dom": "^19.0.0",
93
+ "axios": "^1.19.0",
93
94
  "react": "^19.0.0",
94
95
  "react-dom": "^19.0.0",
95
96
  "tsup": "^8.0.0",