@buoy-gg/network 7.0.39 → 7.0.41
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 +6 -0
- package/lib/commonjs/index.js +7 -0
- package/lib/commonjs/network/components/NetworkListScreen.js +2 -3
- package/lib/commonjs/network/components/NetworkListScreen.web.js +2 -3
- package/lib/commonjs/network/components/NetworkModal.js +1 -1
- package/lib/commonjs/network/conditions/xhr.js +26 -1
- package/lib/commonjs/network/overrides/applyToXhr.js +99 -42
- package/lib/commonjs/network/overrides/matchRule.js +7 -1
- package/lib/commonjs/network/overrides/overriddenRows.js +37 -0
- package/lib/commonjs/network/overrides/resolveOverride.js +1 -1
- package/lib/commonjs/network/utils/networkEventStore.js +24 -6
- package/lib/commonjs/network/utils/networkListener.js +106 -11
- package/lib/commonjs/network/utils/networkSavedStore.js +4 -4
- package/lib/commonjs/network/utils/requestId.js +16 -0
- package/lib/commonjs/network/utils/resolveRequestUrl.js +15 -0
- package/lib/commonjs/network/utils/startNetworkCapture.js +24 -0
- package/lib/commonjs/preset.js +3 -12
- package/lib/module/index.js +1 -0
- package/lib/module/network/components/NetworkListScreen.js +2 -3
- package/lib/module/network/components/NetworkListScreen.web.js +2 -3
- package/lib/module/network/components/NetworkModal.js +1 -1
- package/lib/module/network/conditions/xhr.js +27 -2
- package/lib/module/network/overrides/applyToXhr.js +98 -42
- package/lib/module/network/overrides/matchRule.js +7 -1
- package/lib/module/network/overrides/overriddenRows.js +33 -0
- package/lib/module/network/overrides/resolveOverride.js +1 -1
- package/lib/module/network/utils/networkEventStore.js +25 -7
- package/lib/module/network/utils/networkListener.js +106 -12
- package/lib/module/network/utils/networkSavedStore.js +4 -4
- package/lib/module/network/utils/requestId.js +12 -0
- package/lib/module/network/utils/resolveRequestUrl.js +11 -0
- package/lib/module/network/utils/startNetworkCapture.js +21 -0
- package/lib/module/preset.js +3 -12
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkListScreen.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkListScreen.web.d.ts.map +1 -1
- package/lib/typescript/network/conditions/xhr.d.ts.map +1 -1
- package/lib/typescript/network/overrides/applyToXhr.d.ts +1 -0
- package/lib/typescript/network/overrides/applyToXhr.d.ts.map +1 -1
- package/lib/typescript/network/overrides/matchRule.d.ts.map +1 -1
- package/lib/typescript/network/overrides/overriddenRows.d.ts +16 -0
- package/lib/typescript/network/overrides/overriddenRows.d.ts.map +1 -0
- package/lib/typescript/network/utils/networkEventStore.d.ts.map +1 -1
- package/lib/typescript/network/utils/networkListener.d.ts +3 -4
- package/lib/typescript/network/utils/networkListener.d.ts.map +1 -1
- package/lib/typescript/network/utils/networkSavedStore.d.ts +4 -4
- package/lib/typescript/network/utils/requestId.d.ts +2 -0
- package/lib/typescript/network/utils/requestId.d.ts.map +1 -0
- package/lib/typescript/network/utils/resolveRequestUrl.d.ts +3 -0
- package/lib/typescript/network/utils/resolveRequestUrl.d.ts.map +1 -0
- package/lib/typescript/network/utils/startNetworkCapture.d.ts +6 -0
- package/lib/typescript/network/utils/startNetworkCapture.d.ts.map +1 -0
- package/lib/typescript/preset.d.ts.map +1 -1
- package/lib/web/index.mjs +146 -0
- package/package.json +32 -10
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.nextRequestId = nextRequestId;
|
|
7
|
+
const runtime = globalThis;
|
|
8
|
+
|
|
9
|
+
// Keep identity across Fast Refresh; distinguish requests from earlier app runs.
|
|
10
|
+
function nextRequestId(client) {
|
|
11
|
+
const sequence = runtime.__BUOY_NETWORK_REQUEST_SEQUENCE__ ??= {
|
|
12
|
+
session: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`,
|
|
13
|
+
next: 0
|
|
14
|
+
};
|
|
15
|
+
return `${client}_${sequence.session}_${++sequence.next}`;
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.resolveRequestUrl = resolveRequestUrl;
|
|
7
|
+
/** Resolve browser-relative URLs for capture without changing the request arguments. */
|
|
8
|
+
function resolveRequestUrl(url) {
|
|
9
|
+
if (typeof document === "undefined") return url;
|
|
10
|
+
try {
|
|
11
|
+
return new URL(url, document.baseURI).href;
|
|
12
|
+
} catch {
|
|
13
|
+
return url;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.startNetworkCapture = startNetworkCapture;
|
|
7
|
+
var _networkEventStore = require("./networkEventStore");
|
|
8
|
+
var _overrideRulesStore = require("../overrides/overrideRulesStore");
|
|
9
|
+
/**
|
|
10
|
+
* Keep local capture active while the Network panel is closed.
|
|
11
|
+
* Call in the app being inspected and dispose during unmount or HMR cleanup.
|
|
12
|
+
*/
|
|
13
|
+
function startNetworkCapture() {
|
|
14
|
+
const native = typeof navigator !== "undefined" && navigator.product === "ReactNative";
|
|
15
|
+
if (!native && (typeof window === "undefined" || typeof document === "undefined")) return () => {};
|
|
16
|
+
void _overrideRulesStore.overrideRulesStore.ensureLoaded();
|
|
17
|
+
const unsubscribe = _networkEventStore.networkEventStore.subscribeToEvents(() => {});
|
|
18
|
+
let stopped = false;
|
|
19
|
+
return () => {
|
|
20
|
+
if (stopped) return;
|
|
21
|
+
stopped = true;
|
|
22
|
+
unsubscribe();
|
|
23
|
+
};
|
|
24
|
+
}
|
package/lib/commonjs/preset.js
CHANGED
|
@@ -27,16 +27,6 @@ var _jsxRuntime = require("react/jsx-runtime");
|
|
|
27
27
|
* ```
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
-
/**
|
|
31
|
-
* Load persisted response-override rules as soon as the tool is registered.
|
|
32
|
-
*
|
|
33
|
-
* Deliberately at import time rather than when the Network modal mounts: an
|
|
34
|
-
* override has to fire from app boot, with the tool closed and nothing
|
|
35
|
-
* capturing — that is the whole point of persisting rules ("force this endpoint
|
|
36
|
-
* to 500, then restart and watch the boot path"). Hydrating also re-installs
|
|
37
|
-
* the interceptor if any rule is still active. One storage read, dev-only.
|
|
38
|
-
*/void _overrideRulesStore.overrideRulesStore.ensureLoaded();
|
|
39
|
-
|
|
40
30
|
/**
|
|
41
31
|
* Install interception at IMPORT time and buffer boot traffic.
|
|
42
32
|
*
|
|
@@ -52,8 +42,9 @@ var _jsxRuntime = require("react/jsx-runtime");
|
|
|
52
42
|
* desktop dashboard and web mirrors also evaluate this module and must
|
|
53
43
|
* never intercept their OWN process's traffic.
|
|
54
44
|
* - Dev-only, like the rest of the automatic capture behavior.
|
|
55
|
-
*/
|
|
56
|
-
|
|
45
|
+
*/const isReactNativeRuntime = typeof navigator !== "undefined" && navigator.product === "ReactNative";
|
|
46
|
+
// Browser capture initializes persisted rules explicitly through startNetworkCapture.
|
|
47
|
+
if (isReactNativeRuntime) void _overrideRulesStore.overrideRulesStore.ensureLoaded();
|
|
57
48
|
if (isReactNativeRuntime && typeof __DEV__ !== "undefined" && __DEV__) {
|
|
58
49
|
_networkEventStore.networkEventStore.startBootCapture();
|
|
59
50
|
}
|
package/lib/module/index.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
// PRESET (Primary entry point for users)
|
|
15
15
|
// =============================================================================
|
|
16
16
|
export { networkToolPreset, createNetworkTool } from "./preset";
|
|
17
|
+
export { startNetworkCapture } from "./network/utils/startNetworkCapture";
|
|
17
18
|
|
|
18
19
|
// =============================================================================
|
|
19
20
|
// COMPONENTS (For custom UI implementations)
|
|
@@ -20,7 +20,7 @@ import { NetworkUpgradeBanner } from "./NetworkUpgradeBanner";
|
|
|
20
20
|
import { useNetworkEventList } from "../hooks/useNetworkEventList";
|
|
21
21
|
import { useNetworkSaved } from "../hooks/useNetworkSaved";
|
|
22
22
|
import { useOverrideRules } from "../hooks/useOverrideRules";
|
|
23
|
-
import {
|
|
23
|
+
import { overriddenEvents } from "../overrides/overriddenRows";
|
|
24
24
|
import { splitPinned } from "../utils/withPinnedFirst";
|
|
25
25
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
26
26
|
const keyExtractor = item => item.id;
|
|
@@ -192,8 +192,7 @@ export function NetworkListScreen({
|
|
|
192
192
|
// Paused rules still populate the strip — that's how you find the thing you
|
|
193
193
|
// paused in order to resume it. The header says PAUSED instead.
|
|
194
194
|
const activeRules = rules.filter(rule => rule.enabled && !isRuleSpent(rule));
|
|
195
|
-
const
|
|
196
|
-
const overridden = activeRules.length ? [...pinned, ...rest].filter(isOverridden) : [];
|
|
195
|
+
const overridden = overriddenEvents([...pinned, ...rest], activeRules);
|
|
197
196
|
const overriddenIds = new Set(overridden.map(event => event.id));
|
|
198
197
|
const pinnedRows = pinned.filter(event => !overriddenIds.has(event.id));
|
|
199
198
|
const restRows = overriddenIds.size ? rest.filter(event => !overriddenIds.has(event.id)) : rest;
|
|
@@ -53,7 +53,7 @@ import { NetworkEventItemCompact } from "./NetworkEventItemCompact.web";
|
|
|
53
53
|
import { useNetworkEventList } from "../hooks/useNetworkEventList";
|
|
54
54
|
import { useNetworkSaved } from "../hooks/useNetworkSaved";
|
|
55
55
|
import { useOverrideRules } from "../hooks/useOverrideRules";
|
|
56
|
-
import {
|
|
56
|
+
import { overriddenEvents } from "../overrides/overriddenRows";
|
|
57
57
|
import { splitPinned } from "../utils/withPinnedFirst";
|
|
58
58
|
|
|
59
59
|
// =============================
|
|
@@ -385,8 +385,7 @@ export function NetworkListScreen({
|
|
|
385
385
|
// Hoist anything an active rule covers. Done after `splitPinned` so a request
|
|
386
386
|
// that is both pinned and overridden appears once, in the louder section.
|
|
387
387
|
const activeRules = rules.filter(rule => rule.enabled && !isRuleSpent(rule));
|
|
388
|
-
const
|
|
389
|
-
const overridden = activeRules.length ? [...pinned, ...rest].filter(isOverridden) : [];
|
|
388
|
+
const overridden = overriddenEvents([...pinned, ...rest], activeRules);
|
|
390
389
|
const overriddenIds = new Set(overridden.map(event => event.id));
|
|
391
390
|
const pinnedRows = pinned.filter(event => !overriddenIds.has(event.id));
|
|
392
391
|
const restRows = overriddenIds.size ? rest.filter(event => !overriddenIds.has(event.id)) : rest;
|
|
@@ -496,7 +496,7 @@ function NetworkModalInner({
|
|
|
496
496
|
}) : null, /*#__PURE__*/_jsx(PowerToggleButton, {
|
|
497
497
|
isEnabled: !paused,
|
|
498
498
|
onToggle: () => setPaused(prev => !prev),
|
|
499
|
-
accessibilityLabel: "Pause network
|
|
499
|
+
accessibilityLabel: paused ? "Resume network capture" : "Pause network capture",
|
|
500
500
|
testID: "buoy-network-power"
|
|
501
501
|
}), /*#__PURE__*/_jsx(NetworkClearButton, {
|
|
502
502
|
paused: paused
|
|
@@ -1,9 +1,34 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { canSynthesize, completeNativeOverride } from "../overrides/applyToXhr";
|
|
3
|
+
import { canSynthesize, completeNativeOverride, runOverride } from "../overrides/applyToXhr";
|
|
4
4
|
/** Active simulation uses a send-to-completion deadline, including the wait. */
|
|
5
5
|
export function runConditionXhr(target, url, condition, outcome, dispatch) {
|
|
6
|
-
if (!canSynthesize(target))
|
|
6
|
+
if (!canSynthesize(target)) {
|
|
7
|
+
const rule = {
|
|
8
|
+
id: "condition",
|
|
9
|
+
enabled: true,
|
|
10
|
+
urlPattern: url,
|
|
11
|
+
kind: "delay",
|
|
12
|
+
hits: 0,
|
|
13
|
+
seen: 0,
|
|
14
|
+
createdAt: Date.now()
|
|
15
|
+
};
|
|
16
|
+
const simulation = condition.profile === "offline" ? {
|
|
17
|
+
kind: "fail",
|
|
18
|
+
rule,
|
|
19
|
+
failKind: "network",
|
|
20
|
+
delayMs: 0
|
|
21
|
+
} : outcome ? {
|
|
22
|
+
...outcome,
|
|
23
|
+
delayMs: condition.delayMs + outcome.delayMs
|
|
24
|
+
} : {
|
|
25
|
+
kind: "delay",
|
|
26
|
+
rule,
|
|
27
|
+
delayMs: condition.delayMs
|
|
28
|
+
};
|
|
29
|
+
if (!runOverride(target, url, simulation, dispatch)) throw new Error("Network conditions are unavailable for this XMLHttpRequest implementation.");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
7
32
|
const xhr = target;
|
|
8
33
|
if (xhr.readyState !== 1 || xhr._sent) throw new Error("Request is not open or has already been sent.");
|
|
9
34
|
const started = Date.now();
|
|
@@ -279,6 +279,7 @@ function respondStandard(xhr, url, outcome, isAborted) {
|
|
|
279
279
|
setStandardReadyState(xhr, LOADING);
|
|
280
280
|
if (isAborted()) return;
|
|
281
281
|
setStandardReadyState(xhr, DONE);
|
|
282
|
+
if (isAborted()) return;
|
|
282
283
|
fireEvent(xhr, "load");
|
|
283
284
|
fireEvent(xhr, "loadend");
|
|
284
285
|
}
|
|
@@ -307,60 +308,115 @@ function failStandard(xhr, outcome) {
|
|
|
307
308
|
* `delay` pass-through hands the request to the real network, where the
|
|
308
309
|
* prototype `abort()` must be reachable again.
|
|
309
310
|
*/
|
|
311
|
+
const pendingStandardRequests = new WeakMap();
|
|
312
|
+
export function hasPendingStandardRequest(xhr) {
|
|
313
|
+
return pendingStandardRequests.has(xhr);
|
|
314
|
+
}
|
|
310
315
|
function runStandardOverride(xhr, url, outcome, passThrough) {
|
|
311
|
-
|
|
312
|
-
|
|
316
|
+
const started = Date.now();
|
|
317
|
+
const generation = {};
|
|
318
|
+
let timer;
|
|
319
|
+
let deadline;
|
|
313
320
|
let settled = false;
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
321
|
+
let aborted = false;
|
|
322
|
+
let dispatched = false;
|
|
323
|
+
let timeout = xhr.timeout;
|
|
324
|
+
const originalAbort = xhr.abort;
|
|
325
|
+
const originalOpen = xhr.open;
|
|
326
|
+
const names = ['abort', 'open', 'timeout', 'readyState', 'status', 'statusText', 'response', 'responseText', 'responseURL', 'getAllResponseHeaders', 'getResponseHeader'];
|
|
327
|
+
const descriptors = new Map(names.map(name => [name, Object.getOwnPropertyDescriptor(xhr, name)]));
|
|
328
|
+
let prototype = Object.getPrototypeOf(xhr);
|
|
329
|
+
let nativeTimeout;
|
|
330
|
+
while (prototype && !nativeTimeout) {
|
|
331
|
+
nativeTimeout = Object.getOwnPropertyDescriptor(prototype, 'timeout');
|
|
332
|
+
prototype = Object.getPrototypeOf(prototype);
|
|
333
|
+
}
|
|
334
|
+
const finish = () => {
|
|
335
|
+
settled = true;
|
|
336
|
+
if (pendingStandardRequests.get(xhr) === generation) pendingStandardRequests.delete(xhr);
|
|
337
|
+
clearTimeout(timer);
|
|
338
|
+
clearTimeout(deadline);
|
|
339
|
+
xhr.removeEventListener('loadend', finish);
|
|
340
|
+
};
|
|
341
|
+
const restore = () => {
|
|
342
|
+
finish();
|
|
343
|
+
for (const [name, descriptor] of descriptors) {
|
|
344
|
+
if (descriptor) Object.defineProperty(xhr, name, descriptor);else delete xhr[name];
|
|
320
345
|
}
|
|
346
|
+
xhr.timeout = timeout;
|
|
321
347
|
};
|
|
322
|
-
|
|
348
|
+
const scheduleDeadline = () => {
|
|
349
|
+
clearTimeout(deadline);
|
|
350
|
+
if (dispatched) {
|
|
351
|
+
nativeTimeout?.set?.call(xhr, timeout > 0 ? Math.max(1, timeout - (Date.now() - started)) : 0);
|
|
352
|
+
} else if (timeout > 0) {
|
|
353
|
+
deadline = setTimeout(() => {
|
|
354
|
+
if (settled) return;
|
|
355
|
+
clearTimeout(timer);
|
|
356
|
+
failStandard(xhr, {
|
|
357
|
+
...outcome,
|
|
358
|
+
kind: 'fail',
|
|
359
|
+
failKind: 'timeout'
|
|
360
|
+
});
|
|
361
|
+
finish();
|
|
362
|
+
}, Math.max(0, timeout - (Date.now() - started)));
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
Object.defineProperty(xhr, 'timeout', {
|
|
366
|
+
configurable: true,
|
|
367
|
+
get: () => timeout,
|
|
368
|
+
set: value => {
|
|
369
|
+
timeout = Math.max(0, Number(value) || 0);
|
|
370
|
+
scheduleDeadline();
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
Object.defineProperty(xhr, 'open', {
|
|
374
|
+
configurable: true,
|
|
375
|
+
value: (...args) => {
|
|
376
|
+
restore();
|
|
377
|
+
return Reflect.apply(originalOpen, xhr, args);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
Object.defineProperty(xhr, 'abort', {
|
|
323
381
|
configurable: true,
|
|
324
|
-
writable: true,
|
|
325
382
|
value: () => {
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
clearTimeout(timer);
|
|
331
|
-
timer = null;
|
|
383
|
+
if (dispatched || settled) {
|
|
384
|
+
restore();
|
|
385
|
+
originalAbort.call(xhr);
|
|
386
|
+
return;
|
|
332
387
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
// whatwg's request-error steps, which is also what RN's setReadyState
|
|
336
|
-
// dispatches off `_aborted`.
|
|
388
|
+
aborted = true;
|
|
389
|
+
clearTimeout(timer);
|
|
337
390
|
setStandardReadyState(xhr, DONE);
|
|
338
|
-
fireEvent(xhr,
|
|
339
|
-
fireEvent(xhr,
|
|
391
|
+
fireEvent(xhr, 'abort');
|
|
392
|
+
fireEvent(xhr, 'loadend');
|
|
393
|
+
finish();
|
|
394
|
+
if (!pendingStandardRequests.has(xhr)) defineOwn(xhr, 'readyState', 0);
|
|
340
395
|
}
|
|
341
396
|
});
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
397
|
+
pendingStandardRequests.set(xhr, generation);
|
|
398
|
+
xhr.addEventListener('loadend', finish);
|
|
399
|
+
scheduleDeadline();
|
|
400
|
+
timer = setTimeout(() => {
|
|
401
|
+
if (settled || aborted) return;
|
|
402
|
+
if (outcome.kind === 'delay') {
|
|
403
|
+
dispatched = true;
|
|
404
|
+
scheduleDeadline();
|
|
405
|
+
try {
|
|
406
|
+
passThrough();
|
|
407
|
+
} catch {
|
|
408
|
+
failStandard(xhr, {
|
|
409
|
+
...outcome,
|
|
410
|
+
kind: 'fail',
|
|
411
|
+
failKind: 'network'
|
|
412
|
+
});
|
|
413
|
+
finish();
|
|
414
|
+
}
|
|
350
415
|
return;
|
|
351
416
|
}
|
|
352
|
-
if (outcome.kind ===
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
respondStandard(xhr, url, outcome, () => aborted);
|
|
356
|
-
}
|
|
357
|
-
settled = true;
|
|
358
|
-
restoreAbort();
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
// ALWAYS asynchronous, even at 0ms — same contract as the RN path: callers
|
|
362
|
-
// legitimately attach handlers after `send()` returns (axios does).
|
|
363
|
-
timer = setTimeout(fire, outcome.delayMs);
|
|
417
|
+
if (outcome.kind === 'fail') failStandard(xhr, outcome);else respondStandard(xhr, url, outcome, () => aborted || pendingStandardRequests.get(xhr) !== generation);
|
|
418
|
+
finish();
|
|
419
|
+
}, outcome.delayMs);
|
|
364
420
|
return true;
|
|
365
421
|
}
|
|
366
422
|
|
|
@@ -51,7 +51,13 @@ export function compilePattern(pattern) {
|
|
|
51
51
|
// Anchored, like Chrome's `^${...}$` — a pattern describes the whole URL, so
|
|
52
52
|
// `pokeapi.co` matches nothing and `*pokeapi.co*` matches everything on that
|
|
53
53
|
// host. Predictable beats clever; the rule editor writes full URLs for you.
|
|
54
|
-
|
|
54
|
+
//
|
|
55
|
+
// One exception: a pattern that starts with `/` is a PATH. Anchored to the
|
|
56
|
+
// full URL it could never match anything (every URL starts with a scheme),
|
|
57
|
+
// so it means "this path on any origin" — `/api/rewards` matches
|
|
58
|
+
// `http://localhost:3002/api/rewards`, and `/api/rewards*` also its queries.
|
|
59
|
+
const source = pattern.startsWith("/") ? `[a-zA-Z][a-zA-Z0-9+.-]*://[^/]*${escapeRegex(pattern)}` : escapeRegex(pattern);
|
|
60
|
+
const regex = new RegExp(`^${source}$`);
|
|
55
61
|
// Unbounded growth isn't possible in practice (patterns come from a capped
|
|
56
62
|
// rule list), but a user editing a pattern character by character churns
|
|
57
63
|
// entries, so keep the cache from becoming a leak.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { ruleMatches } from "./matchRule";
|
|
4
|
+
/**
|
|
5
|
+
* Which captured requests belong in the list's OVERRIDDEN strip.
|
|
6
|
+
*
|
|
7
|
+
* A row belongs there when an active rule actually changed it (it carries the
|
|
8
|
+
* rule's override mark) or matched it after the rule existed (while overrides
|
|
9
|
+
* are paused those pass through, and the strip is how you find the rule again).
|
|
10
|
+
* Before a rule existed nothing overrode anything, so older matching requests
|
|
11
|
+
* are real server responses and stay in the list, with one exception: the
|
|
12
|
+
* newest of them, which is the request the rule was built from. That one hoists
|
|
13
|
+
* right away, so creating a rule shows where it points without waiting for the
|
|
14
|
+
* endpoint to be called again.
|
|
15
|
+
*/
|
|
16
|
+
export function overriddenEvents(events, activeRules) {
|
|
17
|
+
if (activeRules.length === 0) return [];
|
|
18
|
+
const activeIds = new Set(activeRules.map(rule => rule.id));
|
|
19
|
+
const picked = new Set();
|
|
20
|
+
for (const rule of activeRules) {
|
|
21
|
+
let source;
|
|
22
|
+
for (const event of events) {
|
|
23
|
+
if (event.override && activeIds.has(event.override.ruleId)) {
|
|
24
|
+
picked.add(event.id);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (!ruleMatches(rule, event.url, String(event.method || "GET"))) continue;
|
|
28
|
+
if (event.timestamp >= rule.createdAt) picked.add(event.id);else if (!source || event.timestamp > source.timestamp) source = event;
|
|
29
|
+
}
|
|
30
|
+
if (source) picked.add(source.id);
|
|
31
|
+
}
|
|
32
|
+
return events.filter(event => picked.has(event.id));
|
|
33
|
+
}
|
|
@@ -23,7 +23,7 @@ import { STATUS_MAX, STATUS_MIN } from "./types";
|
|
|
23
23
|
* "the dev tools stopped working and I don't know why". Metro/debugger traffic
|
|
24
24
|
* is already excluded upstream by `networkListener.shouldIgnoreUrl`.
|
|
25
25
|
*/
|
|
26
|
-
const NEVER_OVERRIDE = [/(^|\.)keygen\.sh/i];
|
|
26
|
+
const NEVER_OVERRIDE = [/(^|\.)keygen\.sh/i, /^https?:\/\/(www\.)?buoy\.gg\/api\/license(\/|$)/i];
|
|
27
27
|
function isProtectedUrl(url) {
|
|
28
28
|
return NEVER_OVERRIDE.some(pattern => pattern.test(url));
|
|
29
29
|
}
|
|
@@ -24,7 +24,7 @@ import { getAccountAccess } from "@buoy-gg/shared-ui";
|
|
|
24
24
|
|
|
25
25
|
import { BaseEventStore } from "@buoy-gg/shared-ui";
|
|
26
26
|
import { extractOperationName } from "./extractOperationName";
|
|
27
|
-
import { networkListener, addNetworkListener } from "./networkListener";
|
|
27
|
+
import { networkListener, addNetworkListener, getNetworkListenerCount, stopNetworkListener } from "./networkListener";
|
|
28
28
|
import { networkSavedStore } from "./networkSavedStore";
|
|
29
29
|
|
|
30
30
|
/**
|
|
@@ -138,9 +138,8 @@ class NetworkEventStore extends BaseEventStore {
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
// If no other listeners exist, stop the network interceptor
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
listener.stopListening();
|
|
141
|
+
if (getNetworkListenerCount() === 0) {
|
|
142
|
+
stopNetworkListener();
|
|
144
143
|
}
|
|
145
144
|
}
|
|
146
145
|
|
|
@@ -153,9 +152,8 @@ class NetworkEventStore extends BaseEventStore {
|
|
|
153
152
|
disableCapture() {
|
|
154
153
|
this.mirrorMode = true;
|
|
155
154
|
this.takeBootBuffer();
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
listener.stopListening();
|
|
155
|
+
if (getNetworkListenerCount() === 0) {
|
|
156
|
+
stopNetworkListener();
|
|
159
157
|
}
|
|
160
158
|
super.disableCapture();
|
|
161
159
|
}
|
|
@@ -202,6 +200,8 @@ class NetworkEventStore extends BaseEventStore {
|
|
|
202
200
|
request
|
|
203
201
|
} = event;
|
|
204
202
|
if (event.type === "request") {
|
|
203
|
+
// Replayed request notifications must not reset a completed row or add a second key.
|
|
204
|
+
if (this.events.some(item => item.id === request.id)) return;
|
|
205
205
|
// Build full URL with query params if present
|
|
206
206
|
const queryString = request.params ? `?${new URLSearchParams(request.params).toString()}` : "";
|
|
207
207
|
const fullUrl = `${request.url}${queryString}`;
|
|
@@ -240,6 +240,24 @@ class NetworkEventStore extends BaseEventStore {
|
|
|
240
240
|
this.events = [networkEvent, ...this.events].slice(0, this.maxEvents);
|
|
241
241
|
this.notify(networkEvent);
|
|
242
242
|
this.notifyArrayListeners();
|
|
243
|
+
} else if (event.type === "request-body") {
|
|
244
|
+
const current = this.events.find(item => item.id === request.id);
|
|
245
|
+
if (!current) return;
|
|
246
|
+
const updated = {
|
|
247
|
+
...current,
|
|
248
|
+
requestData: request.data,
|
|
249
|
+
requestSize: this.getDataSize(request.data)
|
|
250
|
+
};
|
|
251
|
+
if (request.client === "graphql") {
|
|
252
|
+
updated.operationName = extractOperationName(request.data) || undefined;
|
|
253
|
+
const variables = request.data?.variables;
|
|
254
|
+
if (variables && typeof variables === "object") updated.graphqlVariables = variables;
|
|
255
|
+
}
|
|
256
|
+
this.events = this.events.map(item => item.id === request.id ? updated : item);
|
|
257
|
+
if (this.pendingRequests.has(request.id)) this.pendingRequests.set(request.id, updated);
|
|
258
|
+
networkSavedStore.syncFromLive(updated);
|
|
259
|
+
this.notify(updated);
|
|
260
|
+
this.notifyArrayListeners();
|
|
243
261
|
} else if (event.type === "response" || event.type === "error") {
|
|
244
262
|
const index = this.events.findIndex(e => e.id === request.id);
|
|
245
263
|
if (index !== -1) {
|