@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
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MONITOR_ENABLED,
|
|
3
|
+
PERSIST_MAX_ENTRY_BYTES,
|
|
4
|
+
__spreadProps,
|
|
5
|
+
__spreadValues,
|
|
6
|
+
captureFrames,
|
|
7
|
+
configureDbName,
|
|
8
|
+
estimateBytes,
|
|
9
|
+
flattenHeaders,
|
|
10
|
+
getOwner,
|
|
11
|
+
networkMonitor,
|
|
12
|
+
now,
|
|
13
|
+
serializeBody,
|
|
14
|
+
serializeHeaders,
|
|
15
|
+
truncateForPersist
|
|
16
|
+
} from "./chunk-FIYP47QZ.js";
|
|
17
|
+
|
|
18
|
+
// src/capture/monitorStamp.ts
|
|
19
|
+
var MONITOR_ID = /* @__PURE__ */ Symbol.for("blix.monitorId");
|
|
20
|
+
var stampFallback = /* @__PURE__ */ new WeakMap();
|
|
21
|
+
function stampMonitorId(config, id) {
|
|
22
|
+
try {
|
|
23
|
+
Object.defineProperty(config, MONITOR_ID, {
|
|
24
|
+
value: id,
|
|
25
|
+
enumerable: false,
|
|
26
|
+
writable: true,
|
|
27
|
+
configurable: true
|
|
28
|
+
});
|
|
29
|
+
} catch (e) {
|
|
30
|
+
stampFallback.set(config, id);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function readMonitorId(config) {
|
|
34
|
+
if (config === null || typeof config !== "object") return void 0;
|
|
35
|
+
const stamped = config;
|
|
36
|
+
const direct = stamped[MONITOR_ID];
|
|
37
|
+
if (typeof direct === "string") return direct;
|
|
38
|
+
if (typeof stamped.__monitorId === "string") return stamped.__monitorId;
|
|
39
|
+
return stampFallback.get(config);
|
|
40
|
+
}
|
|
41
|
+
var AXIOS_FETCH = /* @__PURE__ */ Symbol.for("blix.axiosFetch");
|
|
42
|
+
function stampAxiosFetchOptions(config, id) {
|
|
43
|
+
try {
|
|
44
|
+
const current = config.fetchOptions;
|
|
45
|
+
if (current != null && typeof current !== "object") return;
|
|
46
|
+
config.fetchOptions = __spreadProps(__spreadValues({}, current), { [AXIOS_FETCH]: id });
|
|
47
|
+
} catch (e) {
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function isAxiosCapturedFetch(init) {
|
|
51
|
+
if (init === null || typeof init !== "object") return false;
|
|
52
|
+
try {
|
|
53
|
+
return init[AXIOS_FETCH] !== void 0;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/capture/attachHttp.ts
|
|
60
|
+
var attached = /* @__PURE__ */ new WeakSet();
|
|
61
|
+
function attachHttpMonitor(instance, options) {
|
|
62
|
+
if (!MONITOR_ENABLED) return () => {
|
|
63
|
+
};
|
|
64
|
+
const managers = instance.interceptors;
|
|
65
|
+
if (attached.has(managers)) return () => {
|
|
66
|
+
};
|
|
67
|
+
attached.add(managers);
|
|
68
|
+
if (options == null ? void 0 : options.dbName) configureDbName(options.dbName);
|
|
69
|
+
const requestId = managers.request.use((config) => {
|
|
70
|
+
var _a, _b;
|
|
71
|
+
if (networkMonitor.isPaused) return config;
|
|
72
|
+
try {
|
|
73
|
+
const id = networkMonitor.nextId();
|
|
74
|
+
config.__monitorId = id;
|
|
75
|
+
stampMonitorId(config, id);
|
|
76
|
+
stampAxiosFetchOptions(config, id);
|
|
77
|
+
const hadFormData = typeof FormData !== "undefined" && config.data instanceof FormData;
|
|
78
|
+
const requestPayload = serializeBody(config.data);
|
|
79
|
+
const owner = getOwner();
|
|
80
|
+
networkMonitor.start({
|
|
81
|
+
id,
|
|
82
|
+
client: "axios",
|
|
83
|
+
method: ((_a = config.method) != null ? _a : "post").toUpperCase(),
|
|
84
|
+
url: (_b = config.url) != null ? _b : "",
|
|
85
|
+
baseURL: config.baseURL,
|
|
86
|
+
at: Date.now(),
|
|
87
|
+
startTime: now(),
|
|
88
|
+
requestPayload,
|
|
89
|
+
requestHeaders: serializeHeaders(config.headers),
|
|
90
|
+
sizeBytes: estimateBytes(requestPayload),
|
|
91
|
+
skipEncryption: config.skipEncryption === true,
|
|
92
|
+
hadFormData,
|
|
93
|
+
replayOf: config.__monitorReplayOf,
|
|
94
|
+
initiator: config.__monitorInitiator,
|
|
95
|
+
ownerId: owner == null ? void 0 : owner.id,
|
|
96
|
+
initiatorKind: owner == null ? void 0 : owner.kind
|
|
97
|
+
});
|
|
98
|
+
if (owner) networkMonitor.linkChild(owner.id, id);
|
|
99
|
+
} catch (e) {
|
|
100
|
+
}
|
|
101
|
+
return config;
|
|
102
|
+
});
|
|
103
|
+
const responseId = managers.response.use(
|
|
104
|
+
(response) => {
|
|
105
|
+
const id = response.config.__monitorId;
|
|
106
|
+
if (!id) return response;
|
|
107
|
+
try {
|
|
108
|
+
const payload = response.data;
|
|
109
|
+
networkMonitor.update(id, {
|
|
110
|
+
state: "success",
|
|
111
|
+
status: response.status,
|
|
112
|
+
endTime: now(),
|
|
113
|
+
responsePayload: payload,
|
|
114
|
+
responseHeaders: serializeHeaders(response.headers),
|
|
115
|
+
sizeBytes: estimateBytes(payload)
|
|
116
|
+
});
|
|
117
|
+
} catch (e) {
|
|
118
|
+
}
|
|
119
|
+
return response;
|
|
120
|
+
},
|
|
121
|
+
(error) => {
|
|
122
|
+
var _a, _b, _c, _d, _e;
|
|
123
|
+
const id = (_a = error.config) == null ? void 0 : _a.__monitorId;
|
|
124
|
+
if (id) {
|
|
125
|
+
try {
|
|
126
|
+
const payload = (_c = (_b = error.response) == null ? void 0 : _b.data) != null ? _c : error.message;
|
|
127
|
+
networkMonitor.update(id, {
|
|
128
|
+
// axios's own `isCancel` is exactly `!!value.__CANCEL__`, set by
|
|
129
|
+
// `CanceledError` for `AbortController` and `CancelToken`
|
|
130
|
+
// cancellation alike — duck-typed because Blix never imports axios
|
|
131
|
+
// at runtime. A timeout is not a cancel, and stays "error".
|
|
132
|
+
state: error.__CANCEL__ ? "aborted" : "error",
|
|
133
|
+
status: (_d = error.response) == null ? void 0 : _d.status,
|
|
134
|
+
endTime: now(),
|
|
135
|
+
error: payload,
|
|
136
|
+
responseHeaders: serializeHeaders((_e = error.response) == null ? void 0 : _e.headers),
|
|
137
|
+
sizeBytes: estimateBytes(payload)
|
|
138
|
+
});
|
|
139
|
+
} catch (e) {
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return Promise.reject(error);
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
return () => {
|
|
146
|
+
managers.request.eject(requestId);
|
|
147
|
+
managers.response.eject(responseId);
|
|
148
|
+
attached.delete(managers);
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/capture/attachFetch.ts
|
|
153
|
+
var DEFAULT_MAX_BODY_BYTES = 5 * 1024 * 1024;
|
|
154
|
+
var DEFAULT_IGNORE = Object.freeze([
|
|
155
|
+
"/_next/",
|
|
156
|
+
"/__nextjs",
|
|
157
|
+
/[?&]_rsc=/,
|
|
158
|
+
".hot-update.",
|
|
159
|
+
"/__webpack_hmr"
|
|
160
|
+
]);
|
|
161
|
+
var WRAPPER_MARK = /* @__PURE__ */ Symbol.for("blix.fetchMonitor");
|
|
162
|
+
var noop = () => {
|
|
163
|
+
};
|
|
164
|
+
function describeBytes(n) {
|
|
165
|
+
if (n >= 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
|
|
166
|
+
if (n >= 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
167
|
+
return `${n} B`;
|
|
168
|
+
}
|
|
169
|
+
function notCaptured(reason) {
|
|
170
|
+
return `\xABbody not captured: ${reason}\xBB`;
|
|
171
|
+
}
|
|
172
|
+
function binarySummary(contentType, bytes) {
|
|
173
|
+
return `\xABbinary body: ${contentType || "unknown type"} \u2014 ${bytes} bytes\xBB`;
|
|
174
|
+
}
|
|
175
|
+
function errorName(error) {
|
|
176
|
+
if (error === null || typeof error !== "object") return void 0;
|
|
177
|
+
const name = error.name;
|
|
178
|
+
return typeof name === "string" ? name : void 0;
|
|
179
|
+
}
|
|
180
|
+
function describeError(error) {
|
|
181
|
+
try {
|
|
182
|
+
if (error !== null && typeof error === "object") {
|
|
183
|
+
const message = error.message;
|
|
184
|
+
const name = errorName(error);
|
|
185
|
+
if (typeof message === "string") return name ? `${name}: ${message}` : message;
|
|
186
|
+
}
|
|
187
|
+
return String(error);
|
|
188
|
+
} catch (e) {
|
|
189
|
+
return "unknown error";
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function headerValue(headers, name) {
|
|
193
|
+
const wanted = name.toLowerCase();
|
|
194
|
+
for (const key of Object.keys(headers)) {
|
|
195
|
+
if (key.toLowerCase() === wanted) return headers[key];
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
function settle(id, patch) {
|
|
200
|
+
try {
|
|
201
|
+
networkMonitor.update(id, patch);
|
|
202
|
+
} catch (e) {
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
var TEXTUAL = /^\s*(?:text\/|application\/(?:json|javascript|ecmascript|xml|x-www-form-urlencoded|graphql|x-ndjson)\b|[^;\s]+\+(?:json|xml)\b)/i;
|
|
206
|
+
var EVENT_STREAM = /^\s*text\/event-stream/i;
|
|
207
|
+
function isTextual(contentType) {
|
|
208
|
+
return !contentType || TEXTUAL.test(contentType);
|
|
209
|
+
}
|
|
210
|
+
function decodeBody(bytes, contentType) {
|
|
211
|
+
try {
|
|
212
|
+
const text = new TextDecoder("utf-8", { fatal: !contentType }).decode(bytes);
|
|
213
|
+
return serializeBody(text);
|
|
214
|
+
} catch (e) {
|
|
215
|
+
return binarySummary(contentType, bytes.byteLength);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async function readCapped(body, cap, retain) {
|
|
219
|
+
const reader = body.getReader();
|
|
220
|
+
const chunks = [];
|
|
221
|
+
let length = 0;
|
|
222
|
+
try {
|
|
223
|
+
for (; ; ) {
|
|
224
|
+
const { done, value } = await reader.read();
|
|
225
|
+
if (done) break;
|
|
226
|
+
length += value.byteLength;
|
|
227
|
+
if (length > cap) {
|
|
228
|
+
reader.cancel().catch(noop);
|
|
229
|
+
return { kind: "over-cap", length };
|
|
230
|
+
}
|
|
231
|
+
if (retain) chunks.push(value);
|
|
232
|
+
}
|
|
233
|
+
} catch (error) {
|
|
234
|
+
return { kind: "failed", error };
|
|
235
|
+
}
|
|
236
|
+
if (!retain) return { kind: "complete", bytes: null, length };
|
|
237
|
+
const bytes = new Uint8Array(length);
|
|
238
|
+
let offset = 0;
|
|
239
|
+
for (const chunk of chunks) {
|
|
240
|
+
bytes.set(chunk, offset);
|
|
241
|
+
offset += chunk.byteLength;
|
|
242
|
+
}
|
|
243
|
+
return { kind: "complete", bytes, length };
|
|
244
|
+
}
|
|
245
|
+
function isRequest(value) {
|
|
246
|
+
return typeof Request !== "undefined" && value instanceof Request;
|
|
247
|
+
}
|
|
248
|
+
function describe(input, rawInit) {
|
|
249
|
+
var _a, _b, _c;
|
|
250
|
+
const request = isRequest(input) ? input : null;
|
|
251
|
+
const init = rawInit !== null && typeof rawInit === "object" ? rawInit : void 0;
|
|
252
|
+
const url = request ? request.url : String(input);
|
|
253
|
+
let resolved = null;
|
|
254
|
+
try {
|
|
255
|
+
resolved = new URL(url, typeof location !== "undefined" ? location.href : void 0);
|
|
256
|
+
} catch (e) {
|
|
257
|
+
resolved = null;
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
url,
|
|
261
|
+
resolved,
|
|
262
|
+
method: String((_b = (_a = init == null ? void 0 : init.method) != null ? _a : request == null ? void 0 : request.method) != null ? _b : "GET").toUpperCase(),
|
|
263
|
+
headers: flattenHeaders((init == null ? void 0 : init.headers) !== void 0 ? init.headers : request == null ? void 0 : request.headers),
|
|
264
|
+
signal: (_c = (init == null ? void 0 : init.signal) !== void 0 ? init.signal : request == null ? void 0 : request.signal) != null ? _c : null,
|
|
265
|
+
request,
|
|
266
|
+
init
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function isIgnored(url, rules) {
|
|
270
|
+
if (!url) return false;
|
|
271
|
+
const target = url.pathname + url.search;
|
|
272
|
+
for (const rule of rules) {
|
|
273
|
+
try {
|
|
274
|
+
if (typeof rule === "string") {
|
|
275
|
+
if (target.includes(rule)) return true;
|
|
276
|
+
} else if (rule instanceof RegExp) {
|
|
277
|
+
rule.lastIndex = 0;
|
|
278
|
+
if (rule.test(target)) return true;
|
|
279
|
+
} else if (typeof rule === "function" && rule(url)) {
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
} catch (e) {
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
function describeBodyValue(body) {
|
|
288
|
+
var _a;
|
|
289
|
+
const plain = (payload, hadFormData = false) => ({
|
|
290
|
+
payload,
|
|
291
|
+
bytes: estimateBytes(body),
|
|
292
|
+
hadFormData,
|
|
293
|
+
clone: null
|
|
294
|
+
});
|
|
295
|
+
if (typeof FormData !== "undefined" && body instanceof FormData) {
|
|
296
|
+
return plain(serializeBody(body), true);
|
|
297
|
+
}
|
|
298
|
+
if (typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams) {
|
|
299
|
+
return plain(body.toString());
|
|
300
|
+
}
|
|
301
|
+
if (typeof ReadableStream !== "undefined" && body instanceof ReadableStream) {
|
|
302
|
+
return {
|
|
303
|
+
payload: notCaptured("streaming request body \u2014 reading it would consume the upload"),
|
|
304
|
+
bytes: 0,
|
|
305
|
+
hadFormData: false,
|
|
306
|
+
clone: null
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
if (typeof File !== "undefined" && body instanceof File) {
|
|
310
|
+
return plain(`\xABFile: ${body.name} \u2014 ${body.size} bytes\xBB`);
|
|
311
|
+
}
|
|
312
|
+
if (typeof Blob !== "undefined" && body instanceof Blob) {
|
|
313
|
+
return plain(`\xABBlob: ${body.type || "untyped"} \u2014 ${body.size} bytes\xBB`);
|
|
314
|
+
}
|
|
315
|
+
if (typeof ArrayBuffer !== "undefined" && (body instanceof ArrayBuffer || ArrayBuffer.isView(body))) {
|
|
316
|
+
const label = (_a = body.constructor) == null ? void 0 : _a.name;
|
|
317
|
+
return plain(`\xAB${label || "ArrayBuffer"} \u2014 ${estimateBytes(body)} bytes\xBB`);
|
|
318
|
+
}
|
|
319
|
+
return plain(serializeBody(body));
|
|
320
|
+
}
|
|
321
|
+
function planRequestBody(outgoing) {
|
|
322
|
+
var _a;
|
|
323
|
+
const none = { payload: void 0, bytes: 0, hadFormData: false, clone: null };
|
|
324
|
+
const initBody = (_a = outgoing.init) == null ? void 0 : _a.body;
|
|
325
|
+
if (initBody != null) return describeBodyValue(initBody);
|
|
326
|
+
const request = outgoing.request;
|
|
327
|
+
if (!request || request.body === null) return none;
|
|
328
|
+
if (request.bodyUsed) {
|
|
329
|
+
return __spreadProps(__spreadValues({}, none), { payload: notCaptured("the request body was already read before fetch was called") });
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
return __spreadProps(__spreadValues({}, none), { clone: request.clone() });
|
|
333
|
+
} catch (e) {
|
|
334
|
+
return __spreadProps(__spreadValues({}, none), { payload: notCaptured("the request body is locked and could not be cloned") });
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function readRequestClone(id, clone, cap, headers) {
|
|
338
|
+
const body = clone.body;
|
|
339
|
+
if (!body) return;
|
|
340
|
+
const contentType = headerValue(headers, "content-type");
|
|
341
|
+
const retain = isTextual(contentType);
|
|
342
|
+
void readCapped(body, cap, retain).then((result) => {
|
|
343
|
+
let requestPayload;
|
|
344
|
+
if (result.kind === "complete") {
|
|
345
|
+
requestPayload = result.bytes ? decodeBody(result.bytes, contentType) : binarySummary(contentType, result.length);
|
|
346
|
+
} else if (result.kind === "over-cap") {
|
|
347
|
+
requestPayload = notCaptured(
|
|
348
|
+
`the request body passed the ${describeBytes(cap)} capture limit`
|
|
349
|
+
);
|
|
350
|
+
} else {
|
|
351
|
+
requestPayload = notCaptured(
|
|
352
|
+
`the request body could not be read (${describeError(result.error)})`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
settle(id, { requestPayload });
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
function begin(outgoing, initiator, cap) {
|
|
359
|
+
const id = networkMonitor.nextId();
|
|
360
|
+
const body = planRequestBody(outgoing);
|
|
361
|
+
const owner = getOwner();
|
|
362
|
+
networkMonitor.start({
|
|
363
|
+
id,
|
|
364
|
+
client: "fetch",
|
|
365
|
+
method: outgoing.method,
|
|
366
|
+
url: outgoing.url,
|
|
367
|
+
at: Date.now(),
|
|
368
|
+
startTime: now(),
|
|
369
|
+
requestPayload: body.payload,
|
|
370
|
+
requestHeaders: serializeHeaders(outgoing.headers),
|
|
371
|
+
sizeBytes: body.bytes,
|
|
372
|
+
hadFormData: body.hadFormData,
|
|
373
|
+
initiator,
|
|
374
|
+
ownerId: owner == null ? void 0 : owner.id,
|
|
375
|
+
initiatorKind: owner == null ? void 0 : owner.kind
|
|
376
|
+
});
|
|
377
|
+
if (owner) networkMonitor.linkChild(owner.id, id);
|
|
378
|
+
if (body.clone) readRequestClone(id, body.clone, cap, outgoing.headers);
|
|
379
|
+
return id;
|
|
380
|
+
}
|
|
381
|
+
function respond(id, response, cap, signal) {
|
|
382
|
+
const headersAt = now();
|
|
383
|
+
if (response.type === "opaque" || response.type === "opaqueredirect") {
|
|
384
|
+
settle(id, {
|
|
385
|
+
state: "success",
|
|
386
|
+
status: response.status,
|
|
387
|
+
endTime: headersAt,
|
|
388
|
+
responseHeaders: {},
|
|
389
|
+
responsePayload: notCaptured(
|
|
390
|
+
response.type === "opaque" ? "opaque response \u2014 a no-cors request hides its status, headers and body from JavaScript" : `opaque redirect \u2014 redirect: "manual" hides the redirect's status, headers and body from JavaScript`
|
|
391
|
+
)
|
|
392
|
+
});
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const ok = response.ok;
|
|
396
|
+
const finish = (payload, extra = {}, endTime = headersAt) => settle(id, __spreadValues(__spreadValues({
|
|
397
|
+
state: ok ? "success" : "error",
|
|
398
|
+
endTime
|
|
399
|
+
}, ok ? { responsePayload: payload } : { error: payload }), extra));
|
|
400
|
+
settle(id, {
|
|
401
|
+
status: response.status,
|
|
402
|
+
responseHeaders: serializeHeaders(flattenHeaders(response.headers))
|
|
403
|
+
});
|
|
404
|
+
if (response.body === null) {
|
|
405
|
+
finish(void 0, { sizeBytes: 0 });
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const contentType = response.headers.get("content-type");
|
|
409
|
+
if (contentType && EVENT_STREAM.test(contentType)) {
|
|
410
|
+
finish(notCaptured("event stream \u2014 it does not end, so reading it would buffer for the life of the connection"));
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const declared = Number(response.headers.get("content-length"));
|
|
414
|
+
const hasDeclared = response.headers.has("content-length") && Number.isFinite(declared);
|
|
415
|
+
if (hasDeclared && declared > cap) {
|
|
416
|
+
finish(
|
|
417
|
+
notCaptured(
|
|
418
|
+
`Content-Length (${describeBytes(declared)}) is over the ${describeBytes(cap)} capture limit`
|
|
419
|
+
),
|
|
420
|
+
{ sizeBytes: declared }
|
|
421
|
+
);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
let clone;
|
|
425
|
+
try {
|
|
426
|
+
clone = response.clone();
|
|
427
|
+
} catch (e) {
|
|
428
|
+
finish(notCaptured("the response body was locked or already read before Blix could clone it"));
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const body = clone.body;
|
|
432
|
+
if (!body) {
|
|
433
|
+
finish(void 0, { sizeBytes: 0 });
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
void readCapped(body, cap, isTextual(contentType)).then((result) => {
|
|
437
|
+
if (result.kind === "complete") {
|
|
438
|
+
finish(
|
|
439
|
+
result.bytes ? decodeBody(result.bytes, contentType) : binarySummary(contentType, result.length),
|
|
440
|
+
{ sizeBytes: result.length },
|
|
441
|
+
now()
|
|
442
|
+
);
|
|
443
|
+
} else if (result.kind === "over-cap") {
|
|
444
|
+
finish(
|
|
445
|
+
notCaptured(
|
|
446
|
+
`the decoded body passed the ${describeBytes(cap)} capture limit while downloading` + (hasDeclared ? ` (Content-Length said ${describeBytes(declared)} \u2014 the compressed size)` : "")
|
|
447
|
+
),
|
|
448
|
+
{ sizeBytes: result.length }
|
|
449
|
+
);
|
|
450
|
+
} else if (signal == null ? void 0 : signal.aborted) {
|
|
451
|
+
settle(id, __spreadValues({
|
|
452
|
+
state: "aborted",
|
|
453
|
+
endTime: now()
|
|
454
|
+
}, ok ? { responsePayload: notCaptured("the request was aborted while the body was downloading") } : { error: notCaptured("the request was aborted while the body was downloading") }));
|
|
455
|
+
} else {
|
|
456
|
+
finish(notCaptured(`the body stream failed while downloading (${describeError(result.error)})`));
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
function fail(id, error, signal) {
|
|
461
|
+
const name = errorName(error);
|
|
462
|
+
const aborted = name !== "TimeoutError" && ((signal == null ? void 0 : signal.aborted) === true || name === "AbortError");
|
|
463
|
+
settle(id, {
|
|
464
|
+
state: aborted ? "aborted" : "error",
|
|
465
|
+
endTime: now(),
|
|
466
|
+
error: describeError(error)
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
function isBlixWrapper(fn) {
|
|
470
|
+
return typeof fn === "function" && fn[WRAPPER_MARK] === true;
|
|
471
|
+
}
|
|
472
|
+
function resolveIgnore(option) {
|
|
473
|
+
if (option === void 0) return DEFAULT_IGNORE;
|
|
474
|
+
if (typeof option !== "function") return option;
|
|
475
|
+
try {
|
|
476
|
+
const rules = option(DEFAULT_IGNORE);
|
|
477
|
+
if (Array.isArray(rules)) return rules;
|
|
478
|
+
} catch (error) {
|
|
479
|
+
if (process.env.NODE_ENV === "development") {
|
|
480
|
+
console.warn("[blix] attachFetchMonitor: the ignore function threw; using the defaults.", error);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return DEFAULT_IGNORE;
|
|
484
|
+
}
|
|
485
|
+
function attachFetchMonitor(options = {}) {
|
|
486
|
+
if (!MONITOR_ENABLED || typeof window === "undefined") return noop;
|
|
487
|
+
const original = globalThis.fetch;
|
|
488
|
+
if (typeof original !== "function") return noop;
|
|
489
|
+
if (isBlixWrapper(original)) return noop;
|
|
490
|
+
if (options.dbName) configureDbName(options.dbName);
|
|
491
|
+
const cap = typeof options.maxBodyBytes === "number" && Number.isFinite(options.maxBodyBytes) && options.maxBodyBytes >= 0 ? options.maxBodyBytes : DEFAULT_MAX_BODY_BYTES;
|
|
492
|
+
const ignore = resolveIgnore(options.ignore);
|
|
493
|
+
const wrapper = function blixFetch(...args) {
|
|
494
|
+
const [input, init] = args;
|
|
495
|
+
let outgoing = null;
|
|
496
|
+
try {
|
|
497
|
+
if (!networkMonitor.isPaused && !isAxiosCapturedFetch(init)) {
|
|
498
|
+
const described = describe(input, init);
|
|
499
|
+
if (!isIgnored(described.resolved, ignore)) outgoing = described;
|
|
500
|
+
}
|
|
501
|
+
} catch (e) {
|
|
502
|
+
outgoing = null;
|
|
503
|
+
}
|
|
504
|
+
if (!outgoing) return Reflect.apply(original, this, args);
|
|
505
|
+
const initiator = captureFrames(1);
|
|
506
|
+
let id = null;
|
|
507
|
+
try {
|
|
508
|
+
id = begin(outgoing, initiator, cap);
|
|
509
|
+
} catch (e) {
|
|
510
|
+
id = null;
|
|
511
|
+
}
|
|
512
|
+
if (id === null) return Reflect.apply(original, this, args);
|
|
513
|
+
const entryId = id;
|
|
514
|
+
const signal = outgoing.signal;
|
|
515
|
+
let pending;
|
|
516
|
+
try {
|
|
517
|
+
pending = Promise.resolve(Reflect.apply(original, this, args));
|
|
518
|
+
} catch (error) {
|
|
519
|
+
fail(entryId, error, signal);
|
|
520
|
+
throw error;
|
|
521
|
+
}
|
|
522
|
+
return pending.then(
|
|
523
|
+
(response) => {
|
|
524
|
+
try {
|
|
525
|
+
respond(entryId, response, cap, signal);
|
|
526
|
+
} catch (e) {
|
|
527
|
+
}
|
|
528
|
+
return response;
|
|
529
|
+
},
|
|
530
|
+
(error) => {
|
|
531
|
+
try {
|
|
532
|
+
fail(entryId, error, signal);
|
|
533
|
+
} catch (e) {
|
|
534
|
+
}
|
|
535
|
+
throw error;
|
|
536
|
+
}
|
|
537
|
+
);
|
|
538
|
+
};
|
|
539
|
+
Object.defineProperty(wrapper, WRAPPER_MARK, {
|
|
540
|
+
value: true,
|
|
541
|
+
enumerable: true,
|
|
542
|
+
writable: false,
|
|
543
|
+
configurable: false
|
|
544
|
+
});
|
|
545
|
+
globalThis.fetch = wrapper;
|
|
546
|
+
let restored = false;
|
|
547
|
+
return () => {
|
|
548
|
+
if (restored) return;
|
|
549
|
+
if (globalThis.fetch === wrapper) {
|
|
550
|
+
globalThis.fetch = original;
|
|
551
|
+
restored = true;
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
if (process.env.NODE_ENV === "development") {
|
|
555
|
+
console.warn(
|
|
556
|
+
"[blix] attachFetchMonitor's disposer left globalThis.fetch alone: something else has replaced it since Blix installed its wrapper, and restoring the original now would silently remove that patch too. Blix's wrapper is still underneath it and keeps capturing."
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// src/capture/captureEncrypted.ts
|
|
563
|
+
var MAX_ENCRYPTED_BYTES = PERSIST_MAX_ENTRY_BYTES;
|
|
564
|
+
var BINARY_PREVIEW_BYTES = 512;
|
|
565
|
+
var HEX = "0123456789abcdef";
|
|
566
|
+
function toBytes(value) {
|
|
567
|
+
if (typeof ArrayBuffer === "undefined") return null;
|
|
568
|
+
if (value instanceof ArrayBuffer) return new Uint8Array(value);
|
|
569
|
+
if (ArrayBuffer.isView(value)) {
|
|
570
|
+
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
571
|
+
}
|
|
572
|
+
return null;
|
|
573
|
+
}
|
|
574
|
+
function labelOf(value) {
|
|
575
|
+
var _a;
|
|
576
|
+
const name = (_a = value.constructor) == null ? void 0 : _a.name;
|
|
577
|
+
return typeof name === "string" && name.length > 0 ? name : "BufferSource";
|
|
578
|
+
}
|
|
579
|
+
function binaryPreview(bytes, label) {
|
|
580
|
+
const limit = Math.min(bytes.length, BINARY_PREVIEW_BYTES);
|
|
581
|
+
let head = "";
|
|
582
|
+
for (let i = 0; i < limit; i += 1) {
|
|
583
|
+
const byte = bytes[i];
|
|
584
|
+
head += HEX[byte >> 4] + HEX[byte & 15];
|
|
585
|
+
}
|
|
586
|
+
return {
|
|
587
|
+
__blixBinary: label,
|
|
588
|
+
byteLength: bytes.length,
|
|
589
|
+
head,
|
|
590
|
+
truncated: bytes.length > limit
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
function normalize(value) {
|
|
594
|
+
const bytes = toBytes(value);
|
|
595
|
+
if (bytes) return binaryPreview(bytes, labelOf(value));
|
|
596
|
+
return truncateForPersist(serializeBody(value), MAX_ENCRYPTED_BYTES).value;
|
|
597
|
+
}
|
|
598
|
+
function captureEncrypted(config, payload) {
|
|
599
|
+
if (!MONITOR_ENABLED) return;
|
|
600
|
+
try {
|
|
601
|
+
const id = readMonitorId(config);
|
|
602
|
+
if (!id) return;
|
|
603
|
+
if (!payload) return;
|
|
604
|
+
const patch = {};
|
|
605
|
+
if (payload.request != null) patch.encryptedRequest = normalize(payload.request);
|
|
606
|
+
if (payload.response != null) patch.encryptedResponse = normalize(payload.response);
|
|
607
|
+
if (patch.encryptedRequest === void 0 && patch.encryptedResponse === void 0) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
networkMonitor.update(id, patch);
|
|
611
|
+
} catch (e) {
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// src/capture/realtimeCapture.ts
|
|
616
|
+
var frameCounter = 0;
|
|
617
|
+
function makeFrame(direction, event, data) {
|
|
618
|
+
frameCounter += 1;
|
|
619
|
+
return {
|
|
620
|
+
id: `f${frameCounter}`,
|
|
621
|
+
at: Date.now(),
|
|
622
|
+
direction,
|
|
623
|
+
event,
|
|
624
|
+
data,
|
|
625
|
+
sizeBytes: estimateBytes(data)
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
function tapRealtimeAdapter(adapter, transport) {
|
|
629
|
+
if (!MONITOR_ENABLED) return adapter;
|
|
630
|
+
let connectionId = null;
|
|
631
|
+
const frame = (direction, event, data) => {
|
|
632
|
+
if (!connectionId) return;
|
|
633
|
+
try {
|
|
634
|
+
networkMonitor.appendFrame(connectionId, makeFrame(direction, event, data));
|
|
635
|
+
} catch (e) {
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
const openConnection = (url) => {
|
|
639
|
+
try {
|
|
640
|
+
const id = networkMonitor.nextId();
|
|
641
|
+
connectionId = id;
|
|
642
|
+
const startTime = now();
|
|
643
|
+
networkMonitor.start({
|
|
644
|
+
id,
|
|
645
|
+
kind: "ws",
|
|
646
|
+
transport,
|
|
647
|
+
method: "WS",
|
|
648
|
+
// Strip the token from the query string — it is a credential, and the
|
|
649
|
+
// panel already masks the equivalent HTTP header.
|
|
650
|
+
url: redactUrl(url),
|
|
651
|
+
at: Date.now(),
|
|
652
|
+
startTime,
|
|
653
|
+
// A connection has no response body; size accrues from its frames.
|
|
654
|
+
sizeBytes: 0,
|
|
655
|
+
frames: []
|
|
656
|
+
});
|
|
657
|
+
return id;
|
|
658
|
+
} catch (e) {
|
|
659
|
+
connectionId = null;
|
|
660
|
+
return null;
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
const closeConnection = (state, reason) => {
|
|
664
|
+
if (!connectionId) return;
|
|
665
|
+
try {
|
|
666
|
+
networkMonitor.update(connectionId, {
|
|
667
|
+
state,
|
|
668
|
+
endTime: now(),
|
|
669
|
+
responsePayload: { closed: reason != null ? reason : "disconnected" }
|
|
670
|
+
});
|
|
671
|
+
} catch (e) {
|
|
672
|
+
}
|
|
673
|
+
connectionId = null;
|
|
674
|
+
};
|
|
675
|
+
return new Proxy(adapter, {
|
|
676
|
+
get(target, prop, receiver) {
|
|
677
|
+
const value = Reflect.get(target, prop, receiver);
|
|
678
|
+
if (typeof value !== "function") return value;
|
|
679
|
+
if (prop === "connect") {
|
|
680
|
+
return (token, url) => {
|
|
681
|
+
openConnection(url);
|
|
682
|
+
frame("system", "connect", { url: redactUrl(url), transport });
|
|
683
|
+
return value.call(target, token, url);
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
if (prop === "disconnect") {
|
|
687
|
+
return () => {
|
|
688
|
+
frame("system", "disconnect", null);
|
|
689
|
+
const result = value.call(target);
|
|
690
|
+
closeConnection("success", "client disconnect");
|
|
691
|
+
return result;
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
if (prop === "subscribe") {
|
|
695
|
+
return (channelName) => {
|
|
696
|
+
frame("out", `subscribe:${channelName}`, { channel: channelName });
|
|
697
|
+
return value.call(target, channelName);
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
if (prop === "onMessage") {
|
|
701
|
+
return (cb) => value.call(target, (payload) => {
|
|
702
|
+
var _a;
|
|
703
|
+
frame("in", (_a = payload == null ? void 0 : payload.event) != null ? _a : "message", payload == null ? void 0 : payload.data);
|
|
704
|
+
cb(payload);
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
if (prop === "onPresenceUpdate") {
|
|
708
|
+
return (cb) => value.call(target, (status) => {
|
|
709
|
+
frame("system", `presence:${status}`, { status });
|
|
710
|
+
if (status === "offline") closeConnection("error", "transport offline");
|
|
711
|
+
cb(status);
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
return value.bind(target);
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
function redactUrl(url) {
|
|
719
|
+
try {
|
|
720
|
+
const parsed = new URL(url, "http://local");
|
|
721
|
+
for (const key of ["token", "access_token", "accessToken", "auth"]) {
|
|
722
|
+
if (parsed.searchParams.has(key)) parsed.searchParams.set(key, "\u2022\u2022\u2022");
|
|
723
|
+
}
|
|
724
|
+
return url.startsWith("/") ? parsed.pathname + parsed.search : parsed.toString();
|
|
725
|
+
} catch (e) {
|
|
726
|
+
return url.replace(/([?&](?:token|access_token|accessToken|auth)=)[^&]*/gi, "$1\u2022\u2022\u2022");
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
export {
|
|
731
|
+
attachHttpMonitor,
|
|
732
|
+
attachFetchMonitor,
|
|
733
|
+
captureEncrypted,
|
|
734
|
+
tapRealtimeAdapter
|
|
735
|
+
};
|