@buoy-gg/network 7.0.36 → 7.0.39

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.
Files changed (91) hide show
  1. package/README.md +45 -12
  2. package/lib/commonjs/index.js +26 -1
  3. package/lib/commonjs/index.web.js +26 -1
  4. package/lib/commonjs/network/components/NetworkEventDetailView.js +6 -1
  5. package/lib/commonjs/network/components/NetworkHeaderMenu.js +4 -0
  6. package/lib/commonjs/network/components/NetworkModal.js +24 -8
  7. package/lib/commonjs/network/conditions/NetworkConditionsControl.js +235 -0
  8. package/lib/commonjs/network/conditions/NetworkConditionsControl.test.js +136 -0
  9. package/lib/commonjs/network/conditions/NetworkConditionsIcon.js +33 -0
  10. package/lib/commonjs/network/conditions/NetworkFloatingTool.js +310 -0
  11. package/lib/commonjs/network/conditions/NetworkFloatingTool.test.js +219 -0
  12. package/lib/commonjs/network/conditions/store.js +75 -0
  13. package/lib/commonjs/network/conditions/wait.js +43 -0
  14. package/lib/commonjs/network/conditions/xhr.js +110 -0
  15. package/lib/commonjs/network/hooks/useNetworkEvents.js +1 -1
  16. package/lib/commonjs/network/overrides/applyToXhr.js +10 -8
  17. package/lib/commonjs/network/overrides/engine.js +2 -1
  18. package/lib/commonjs/network/sync/networkSyncAdapter.js +26 -1
  19. package/lib/commonjs/network/utils/generateNetworkCopyText.js +2 -0
  20. package/lib/commonjs/network/utils/networkEventStore.js +5 -77
  21. package/lib/commonjs/network/utils/networkListener.js +96 -51
  22. package/lib/commonjs/network/utils/networkSavedStore.js +2 -2
  23. package/lib/commonjs/network/utils/selectSavedEvents.js +2 -2
  24. package/lib/commonjs/preset.js +2 -1
  25. package/lib/module/index.js +2 -1
  26. package/lib/module/index.web.js +2 -1
  27. package/lib/module/network/components/NetworkEventDetailView.js +6 -1
  28. package/lib/module/network/components/NetworkHeaderMenu.js +4 -0
  29. package/lib/module/network/components/NetworkModal.js +25 -9
  30. package/lib/module/network/conditions/NetworkConditionsControl.js +231 -0
  31. package/lib/module/network/conditions/NetworkConditionsControl.test.js +135 -0
  32. package/lib/module/network/conditions/NetworkConditionsIcon.js +29 -0
  33. package/lib/module/network/conditions/NetworkFloatingTool.js +304 -0
  34. package/lib/module/network/conditions/NetworkFloatingTool.test.js +218 -0
  35. package/lib/module/network/conditions/store.js +65 -0
  36. package/lib/module/network/conditions/wait.js +38 -0
  37. package/lib/module/network/conditions/xhr.js +106 -0
  38. package/lib/module/network/hooks/useNetworkEvents.js +1 -1
  39. package/lib/module/network/overrides/applyToXhr.js +9 -8
  40. package/lib/module/network/overrides/engine.js +2 -1
  41. package/lib/module/network/sync/networkSyncAdapter.js +26 -1
  42. package/lib/module/network/utils/generateNetworkCopyText.js +2 -0
  43. package/lib/module/network/utils/networkEventStore.js +6 -77
  44. package/lib/module/network/utils/networkListener.js +97 -52
  45. package/lib/module/network/utils/networkSavedStore.js +2 -2
  46. package/lib/module/network/utils/selectSavedEvents.js +2 -2
  47. package/lib/module/preset.js +3 -2
  48. package/lib/typescript/index.d.ts +2 -0
  49. package/lib/typescript/index.d.ts.map +1 -1
  50. package/lib/typescript/index.web.d.ts +2 -0
  51. package/lib/typescript/index.web.d.ts.map +1 -1
  52. package/lib/typescript/network/components/NetworkEventDetailView.d.ts.map +1 -1
  53. package/lib/typescript/network/components/NetworkHeaderMenu.d.ts +2 -1
  54. package/lib/typescript/network/components/NetworkHeaderMenu.d.ts.map +1 -1
  55. package/lib/typescript/network/components/NetworkModal.d.ts +3 -0
  56. package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -1
  57. package/lib/typescript/network/conditions/NetworkConditionsControl.d.ts +12 -0
  58. package/lib/typescript/network/conditions/NetworkConditionsControl.d.ts.map +1 -0
  59. package/lib/typescript/network/conditions/NetworkConditionsControl.test.d.ts +2 -0
  60. package/lib/typescript/network/conditions/NetworkConditionsControl.test.d.ts.map +1 -0
  61. package/lib/typescript/network/conditions/NetworkConditionsIcon.d.ts +4 -0
  62. package/lib/typescript/network/conditions/NetworkConditionsIcon.d.ts.map +1 -0
  63. package/lib/typescript/network/conditions/NetworkFloatingTool.d.ts +15 -0
  64. package/lib/typescript/network/conditions/NetworkFloatingTool.d.ts.map +1 -0
  65. package/lib/typescript/network/conditions/NetworkFloatingTool.test.d.ts +2 -0
  66. package/lib/typescript/network/conditions/NetworkFloatingTool.test.d.ts.map +1 -0
  67. package/lib/typescript/network/conditions/store.d.ts +30 -0
  68. package/lib/typescript/network/conditions/store.d.ts.map +1 -0
  69. package/lib/typescript/network/conditions/wait.d.ts +4 -0
  70. package/lib/typescript/network/conditions/wait.d.ts.map +1 -0
  71. package/lib/typescript/network/conditions/xhr.d.ts +5 -0
  72. package/lib/typescript/network/conditions/xhr.d.ts.map +1 -0
  73. package/lib/typescript/network/hooks/useNetworkEvents.d.ts +1 -1
  74. package/lib/typescript/network/overrides/applyToXhr.d.ts +2 -0
  75. package/lib/typescript/network/overrides/applyToXhr.d.ts.map +1 -1
  76. package/lib/typescript/network/overrides/engine.d.ts +0 -10
  77. package/lib/typescript/network/overrides/engine.d.ts.map +1 -1
  78. package/lib/typescript/network/sync/networkSyncAdapter.d.ts +17 -0
  79. package/lib/typescript/network/sync/networkSyncAdapter.d.ts.map +1 -1
  80. package/lib/typescript/network/types/index.d.ts +1 -0
  81. package/lib/typescript/network/types/index.d.ts.map +1 -1
  82. package/lib/typescript/network/utils/generateNetworkCopyText.d.ts.map +1 -1
  83. package/lib/typescript/network/utils/networkEventStore.d.ts +0 -15
  84. package/lib/typescript/network/utils/networkEventStore.d.ts.map +1 -1
  85. package/lib/typescript/network/utils/networkListener.d.ts +2 -1
  86. package/lib/typescript/network/utils/networkListener.d.ts.map +1 -1
  87. package/lib/typescript/network/utils/networkSavedStore.d.ts +2 -2
  88. package/lib/typescript/network/utils/selectSavedEvents.d.ts +2 -2
  89. package/lib/typescript/preset.d.ts +0 -16
  90. package/lib/typescript/preset.d.ts.map +1 -1
  91. package/package.json +2 -2
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.networkConditionsRuntimeId = exports.getNetworkCondition = exports.conditionsCanApply = exports.NETWORK_CONDITIONS = void 0;
7
+ exports.setNetworkCondition = setNetworkCondition;
8
+ exports.snapshotNetworkCondition = snapshotNetworkCondition;
9
+ exports.subscribeNetworkConditions = void 0;
10
+ var _sharedUi = require("@buoy-gg/shared-ui");
11
+ var _networkListener = require("../utils/networkListener");
12
+ const NETWORK_CONDITIONS = exports.NETWORK_CONDITIONS = {
13
+ normal: {
14
+ label: "Normal",
15
+ delayMs: 0
16
+ },
17
+ offline: {
18
+ label: "Offline (requests)",
19
+ delayMs: 0
20
+ },
21
+ slow: {
22
+ label: "Slow (+500 ms)",
23
+ delayMs: 500
24
+ },
25
+ verySlow: {
26
+ label: "Very slow (+2000 ms)",
27
+ delayMs: 2000
28
+ }
29
+ };
30
+ const networkConditionsRuntimeId = exports.networkConditionsRuntimeId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
31
+ let profile = "normal";
32
+ let keepAlive;
33
+ const listeners = new Set();
34
+ const conditionsCanApply = () => typeof __DEV__ !== "undefined" && __DEV__ && (0, _sharedUi.hasAccountAccess)();
35
+ exports.conditionsCanApply = conditionsCanApply;
36
+ const getNetworkCondition = () => profile;
37
+ exports.getNetworkCondition = getNetworkCondition;
38
+ const subscribeNetworkConditions = listener => {
39
+ listeners.add(listener);
40
+ return () => {
41
+ listeners.delete(listener);
42
+ };
43
+ };
44
+ exports.subscribeNetworkConditions = subscribeNetworkConditions;
45
+ function setNetworkCondition(value) {
46
+ if (typeof value !== "string" || !Object.prototype.hasOwnProperty.call(NETWORK_CONDITIONS, value)) {
47
+ throw new Error("Choose normal, offline, slow, or verySlow.");
48
+ }
49
+ if (value !== "normal" && !conditionsCanApply()) {
50
+ throw new Error("Network conditions require an admitted account and a development build.");
51
+ }
52
+ if (value === profile) return profile;
53
+ if (value !== "normal" && !keepAlive) {
54
+ (0, _networkListener.startNetworkListener)();
55
+ keepAlive = (0, _networkListener.addNetworkListener)(() => {});
56
+ } else if (value === "normal") {
57
+ keepAlive?.();
58
+ keepAlive = undefined;
59
+ if ((0, _networkListener.getNetworkListenerCount)() === 0) (0, _networkListener.stopNetworkListener)();
60
+ }
61
+ profile = value;
62
+ listeners.forEach(listener => listener());
63
+ return profile;
64
+ }
65
+ function snapshotNetworkCondition(url) {
66
+ if (profile === "normal" || !conditionsCanApply() || !/^https?:\/\//i.test(url)) return;
67
+ return Object.freeze({
68
+ profile,
69
+ delayMs: NETWORK_CONDITIONS[profile].delayMs
70
+ });
71
+ }
72
+ (0, _sharedUi.subscribeAccountAccess)(() => {
73
+ if (!(0, _sharedUi.hasAccountAccess)()) setNetworkCondition("normal");
74
+ listeners.forEach(listener => listener());
75
+ });
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.throwIfAborted = throwIfAborted;
7
+ exports.waitBeforeDispatch = waitBeforeDispatch;
8
+ function throwIfAborted(signal) {
9
+ if (!signal?.aborted) return;
10
+ if (signal.reason !== undefined) throw signal.reason;
11
+ const error = new Error("The request was aborted.");
12
+ error.name = "AbortError";
13
+ throw error;
14
+ }
15
+
16
+ /** Wait before dispatch, releasing the timer and listener on cancellation. */
17
+ function waitBeforeDispatch(ms, signal) {
18
+ return new Promise((resolve, reject) => {
19
+ try {
20
+ throwIfAborted(signal);
21
+ } catch (error) {
22
+ reject(error);
23
+ return;
24
+ }
25
+ const cleanup = () => signal?.removeEventListener("abort", abort);
26
+ const abort = () => {
27
+ clearTimeout(timer);
28
+ cleanup();
29
+ try {
30
+ throwIfAborted(signal);
31
+ } catch (error) {
32
+ reject(error);
33
+ }
34
+ };
35
+ const timer = setTimeout(() => {
36
+ cleanup();
37
+ resolve();
38
+ }, ms);
39
+ signal?.addEventListener("abort", abort, {
40
+ once: true
41
+ });
42
+ });
43
+ }
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.runConditionXhr = runConditionXhr;
7
+ var _applyToXhr = require("../overrides/applyToXhr");
8
+ /** Active simulation uses a send-to-completion deadline, including the wait. */
9
+ function runConditionXhr(target, url, condition, outcome, dispatch) {
10
+ if (!(0, _applyToXhr.canSynthesize)(target)) throw new Error("Network conditions require React Native XMLHttpRequest.");
11
+ const xhr = target;
12
+ if (xhr.readyState !== 1 || xhr._sent) throw new Error("Request is not open or has already been sent.");
13
+ const started = Date.now();
14
+ let timeout = xhr.timeout;
15
+ let settled = false;
16
+ let dispatching = false;
17
+ let deadline;
18
+ let pending;
19
+ const originalAbort = xhr.abort;
20
+ const originalOpen = xhr.open;
21
+ const abortDescriptor = Object.getOwnPropertyDescriptor(xhr, "abort");
22
+ const openDescriptor = Object.getOwnPropertyDescriptor(xhr, "open");
23
+ const timeoutDescriptor = Object.getOwnPropertyDescriptor(xhr, "timeout");
24
+ const restore = (key, descriptor) => {
25
+ if (descriptor) Object.defineProperty(xhr, key, descriptor);else delete xhr[key];
26
+ };
27
+ const cleanup = () => {
28
+ if (settled) return;
29
+ settled = true;
30
+ clearTimeout(pending);
31
+ clearTimeout(deadline);
32
+ xhr.removeEventListener("loadend", cleanup);
33
+ restore("abort", abortDescriptor);
34
+ restore("open", openDescriptor);
35
+ restore("timeout", timeoutDescriptor);
36
+ xhr.timeout = timeout;
37
+ };
38
+ const fail = timedOut => {
39
+ if (settled) return;
40
+ // DONE suppresses abort events while the native transport is cancelled.
41
+ Object.assign(xhr, {
42
+ readyState: 4
43
+ });
44
+ originalAbort.call(xhr);
45
+ xhr._aborted = false;
46
+ xhr._hasError = true;
47
+ xhr._timedOut = timedOut;
48
+ xhr._response = timedOut ? "Request timed out (Buoy conditions)" : "Network request failed (Buoy Offline)";
49
+ xhr.setReadyState(4);
50
+ cleanup();
51
+ };
52
+ const expired = () => timeout > 0 && Date.now() - started >= timeout;
53
+ const scheduleDeadline = () => {
54
+ clearTimeout(deadline);
55
+ if (timeout > 0) deadline = setTimeout(() => fail(true), Math.max(0, timeout - (Date.now() - started)));
56
+ };
57
+ Object.defineProperty(xhr, "timeout", {
58
+ configurable: true,
59
+ get: () => dispatching ? 0 : timeout,
60
+ set: value => {
61
+ timeout = Math.max(0, Number(value) || 0);
62
+ scheduleDeadline();
63
+ }
64
+ });
65
+ Object.defineProperty(xhr, "abort", {
66
+ configurable: true,
67
+ value: () => {
68
+ if (!settled) {
69
+ originalAbort.call(xhr);
70
+ cleanup();
71
+ }
72
+ }
73
+ });
74
+ Object.defineProperty(xhr, "open", {
75
+ configurable: true,
76
+ value: (...args) => {
77
+ const result = Reflect.apply(originalOpen, xhr, args);
78
+ cleanup();
79
+ return result;
80
+ }
81
+ });
82
+ xhr._sent = true;
83
+ xhr._requestId = null;
84
+ xhr.addEventListener("loadend", cleanup);
85
+ scheduleDeadline();
86
+ pending = setTimeout(() => {
87
+ if (settled) return;
88
+ if (expired()) {
89
+ fail(true);
90
+ return;
91
+ }
92
+ if (condition.profile === "offline") {
93
+ fail(false);
94
+ return;
95
+ }
96
+ if (outcome && outcome.kind !== "delay") {
97
+ (0, _applyToXhr.completeNativeOverride)(xhr, url, outcome);
98
+ return;
99
+ }
100
+ xhr._sent = false;
101
+ dispatching = true;
102
+ try {
103
+ dispatch();
104
+ } catch {
105
+ fail(false);
106
+ } finally {
107
+ dispatching = false;
108
+ }
109
+ }, condition.delayMs + (outcome?.delayMs ?? 0));
110
+ }
@@ -18,7 +18,7 @@ var _formatGraphQLVariables = require("../utils/formatGraphQLVariables");
18
18
 
19
19
  /**
20
20
  * @deprecated Read the live limit with `useLimit("network.requests")` — this
21
- * only reports the FREE tier's value and is blind to the anonymous tier. Kept
21
+ * only reports the FREE tier's value and ignores the Weekend Pass. Kept
22
22
  * because it is part of the package's public exports.
23
23
  */
24
24
  const FREE_TIER_REQUEST_LIMIT = exports.FREE_TIER_REQUEST_LIMIT = _sharedUi.TIER_LIMITS["network.requests"].free;
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.canSynthesize = canSynthesize;
7
7
  exports.canSynthesizeStandard = canSynthesizeStandard;
8
+ exports.completeNativeOverride = completeNativeOverride;
8
9
  exports.runOverride = runOverride;
9
10
  /**
10
11
  * Perform an override outcome against an XMLHttpRequest — React Native's OR a
@@ -406,16 +407,12 @@ function runOverride(xhr, url, outcome, passThrough) {
406
407
  // had already aborted. Claiming it means abort() dispatches DONE + 'abort'
407
408
  // and whatwg-fetch rejects with AbortError, as it should.
408
409
  internals._sent = true;
409
- if (outcome.kind !== "delay") {
410
- // Buoy's own `open()` swizzle overwrites RN's private `_requestId` with a
411
- // string id. RN normally clobbers it back with the native numeric id inside
412
- // `send()` — which we're skipping — so it would survive here, and
413
- // `abort()` would hand that string to `RCTNetworking.abortRequest()`.
414
- // Nulling it makes abort a clean no-op on the native side.
415
- internals._requestId = null;
416
- }
410
+
411
+ // There is no native request to cancel until a delayed send is dispatched.
412
+ internals._requestId = null;
417
413
  let timer = null;
418
414
  const onAbort = () => {
415
+ xhr.removeEventListener("abort", onAbort);
419
416
  if (timer !== null) {
420
417
  clearTimeout(timer);
421
418
  timer = null;
@@ -448,4 +445,9 @@ function runOverride(xhr, url, outcome, passThrough) {
448
445
  // 'load' before they exist.
449
446
  timer = setTimeout(fire, outcome.delayMs);
450
447
  return true;
448
+ }
449
+
450
+ /** Complete an already scheduled native outcome without adding another timer. */
451
+ function completeNativeOverride(xhr, url, outcome) {
452
+ if (outcome.kind === "fail") fail(xhr, outcome);else if (outcome.kind === "respond") respond(xhr, url, outcome);
451
453
  }
@@ -8,6 +8,7 @@ exports.inspectEngine = inspectEngine;
8
8
  exports.overrideForRequest = overrideForRequest;
9
9
  exports.overridesCanApply = overridesCanApply;
10
10
  exports.setOverrideHooks = setOverrideHooks;
11
+ var _sharedUi = require("@buoy-gg/shared-ui");
11
12
  var _resolveOverride = require("./resolveOverride");
12
13
  /**
13
14
  * The seam between the interceptor and the rule store.
@@ -42,7 +43,7 @@ function setOverrideHooks(next) {
42
43
  function overridesCanApply() {
43
44
  // `__DEV__` is undefined outside React Native (unit tests, the desktop
44
45
  // bundle); only an explicit `false` — a real release build — disables it.
45
- return !(typeof __DEV__ !== "undefined" && !__DEV__);
46
+ return (0, _sharedUi.hasAccountAccess)() && !(typeof __DEV__ !== "undefined" && !__DEV__);
46
47
  }
47
48
 
48
49
  /** Why overrides are inert, for a caller that has to explain it to a person. */
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.networkSyncAdapter = exports.SNAPSHOT_HEADER_VALUE_LIMIT = exports.SNAPSHOT_EVENTS_BUDGET = exports.SNAPSHOT_BODY_INLINE_LIMIT = void 0;
7
+ var _store = require("../conditions/store");
7
8
  var _networkEventStore = require("../utils/networkEventStore");
8
9
  var _networkSavedStore = require("../utils/networkSavedStore");
9
10
  var _overrideRulesStore = require("../overrides/overrideRulesStore");
@@ -476,10 +477,13 @@ function baseFromRequest(params) {
476
477
  * `NetworkPanel`/`NetworkInline` accept either shape.
477
478
  */
478
479
  const networkSyncAdapter = exports.networkSyncAdapter = {
480
+ dispose: () => {
481
+ _overrideRulesStore.overrideRulesStore.setEnabled(false);
482
+ },
479
483
  // v5: honest request/responseSize + explicit request/responseBodyOmitted
480
484
  // flags, and a total snapshot budget so the list degrades instead of the
481
485
  // whole panel being dropped at the emit layer.
482
- version: 5,
486
+ version: 6,
483
487
  getSnapshot: () => {
484
488
  // Piggyback the interception health check on the watch loop: while a
485
489
  // dashboard/MCP watch is open this runs continuously, so a clobbered
@@ -488,6 +492,12 @@ const networkSyncAdapter = exports.networkSyncAdapter = {
488
492
  // reads when healthy.
489
493
  _networkEventStore.networkEventStore.ensureInterception();
490
494
  return {
495
+ conditions: {
496
+ version: 1,
497
+ runtimeId: _store.networkConditionsRuntimeId,
498
+ profile: (0, _store.getNetworkCondition)(),
499
+ available: (0, _store.conditionsCanApply)()
500
+ },
491
501
  events: fitEventsToBudget(_networkEventStore.networkEventStore.getEvents()),
492
502
  /**
493
503
  * Response-override rules live on the DEVICE — that's the only place they
@@ -508,17 +518,32 @@ const networkSyncAdapter = exports.networkSyncAdapter = {
508
518
  // Pinning with no traffic in flight still has to reach the dashboard, so
509
519
  // both stores drive the snapshot.
510
520
  const unsubscribeEvents = _networkEventStore.networkEventStore.subscribeToEvents(onChange);
521
+ const unsubscribeConditions = (0, _store.subscribeNetworkConditions)(onChange);
511
522
  const unsubscribeSaved = _networkSavedStore.networkSavedStore.subscribe(onChange);
512
523
  // Same reasoning for rules: editing one with no traffic in flight must
513
524
  // still reach the dashboard. Subscribing also hydrates the rule list.
514
525
  const unsubscribeOverrides = _overrideRulesStore.overrideRulesStore.subscribe(onChange);
515
526
  return () => {
527
+ unsubscribeConditions();
516
528
  unsubscribeEvents();
517
529
  unsubscribeSaved();
518
530
  unsubscribeOverrides();
519
531
  };
520
532
  },
521
533
  actions: {
534
+ getNetworkConditions: () => ({
535
+ runtimeId: _store.networkConditionsRuntimeId,
536
+ profile: (0, _store.getNetworkCondition)(),
537
+ available: (0, _store.conditionsCanApply)()
538
+ }),
539
+ setNetworkConditions: params => {
540
+ const profile = params && typeof params === "object" ? params.profile : undefined;
541
+ return {
542
+ runtimeId: _store.networkConditionsRuntimeId,
543
+ profile: (0, _store.setNetworkCondition)(profile),
544
+ available: (0, _store.conditionsCanApply)()
545
+ };
546
+ },
522
547
  clearEvents: () => {
523
548
  _networkEventStore.networkEventStore.clearEvents();
524
549
  },
@@ -81,6 +81,7 @@ function generateNetworkCopyText(events, copySettings) {
81
81
  obj.statusText = event.statusText;
82
82
  }
83
83
  if (copySettings.includeDuration) obj.duration = event.duration;
84
+ if (event.condition) obj.condition = event.condition;
84
85
  if (copySettings.includeTimestamp) obj.timestamp = event.timestamp;
85
86
  if (copySettings.includeClient) obj.requestClient = event.requestClient;
86
87
  if (copySettings.includeSizes) {
@@ -119,6 +120,7 @@ function generateNetworkCopyText(events, copySettings) {
119
120
  if (copySettings.includeClient && event.requestClient) {
120
121
  md += `**Client:** ${event.requestClient}\n`;
121
122
  }
123
+ if (event.condition) md += `Conditions: ${event.condition.profile} (+${event.condition.delayMs} ms)\n`;
122
124
  if (copySettings.includeDuration && event.duration) {
123
125
  md += `**Duration:** ${event.duration}ms\n`;
124
126
  }
@@ -46,14 +46,6 @@ var _networkSavedStore = require("./networkSavedStore");
46
46
  const BOOT_BUFFER_MAX = 200;
47
47
  class NetworkEventStore extends _sharedUi.BaseEventStore {
48
48
  pendingRequests = new Map();
49
- /**
50
- * Recently seen `METHOD:url` keys → when, and which event id we kept.
51
- *
52
- * The timestamp powers the dual-interception dedup below. The id is what lets
53
- * a DROPPED duplicate still contribute something the kept event doesn't know
54
- * — see `applyOverrideMark`.
55
- */
56
- recentRequests = new Map();
57
49
  rawListenerUnsubscribe = null;
58
50
  /** Raw events captured before the first consumer subscribed — see startBootCapture. */
59
51
  bootBuffer = [];
@@ -85,6 +77,7 @@ class NetworkEventStore extends _sharedUi.BaseEventStore {
85
77
  * state therefore never gains events it didn't ask for).
86
78
  */
87
79
  startBootCapture() {
80
+ if ((0, _sharedUi.getAccountAccess)().status === "locked") return;
88
81
  if (this.mirrorMode || this.bootBufferUnsubscribe || this.isCapturing()) {
89
82
  return;
90
83
  }
@@ -139,6 +132,7 @@ class NetworkEventStore extends _sharedUi.BaseEventStore {
139
132
  * Stop capturing network events
140
133
  */
141
134
  stopCapturing() {
135
+ this.takeBootBuffer();
142
136
  // Remove our raw listener
143
137
  if (this.rawListenerUnsubscribe) {
144
138
  this.rawListenerUnsubscribe();
@@ -205,57 +199,11 @@ class NetworkEventStore extends _sharedUi.BaseEventStore {
205
199
  * Process a network listener event
206
200
  */
207
201
  processNetworkEvent(event) {
202
+ if ((0, _sharedUi.getAccountAccess)().status === "locked") return;
208
203
  const {
209
204
  request
210
205
  } = event;
211
206
  if (event.type === "request") {
212
- // Check for duplicate request based on URL, method, and timing.
213
- // The clock is the event's CAPTURE time, not Date.now(): boot-buffered
214
- // events are flushed in one synchronous burst, and wall-clock "now"
215
- // would put two genuinely separate boot requests to the same endpoint
216
- // inside the 50ms dual-interception window, silently dropping one. For
217
- // live events the two clocks are the same (the raw event is emitted
218
- // synchronously at capture).
219
- const requestKey = `${request.method}:${request.url}`;
220
- const now = event.timestamp instanceof Date ? event.timestamp.getTime() : Date.now();
221
- const recent = this.recentRequests.get(requestKey);
222
-
223
- // If same request within 50ms, likely a duplicate from XHR/fetch dual
224
- // interception. The age must be non-negative: Date.now() is not
225
- // monotonic, and after a backwards clock adjustment (iOS time sync) a
226
- // future-dated entry would satisfy `< 50` for EVERY request to this
227
- // endpoint until the clock caught up — capture going silent for minutes
228
- // to hours, "fixed" only by Clear wiping this map (field-reported). A
229
- // negative age falls through and re-stamps the entry with the new clock.
230
- if (recent && now - recent.at >= 0 && now - recent.at < 50) {
231
- // …but the duplicate is not always redundant. On React Native a
232
- // `fetch()` is seen twice: once by the fetch wrapper, which is what
233
- // creates this event, and again by the XHR wrapper underneath it —
234
- // and ONLY the XHR layer resolves overrides, because that's where the
235
- // request can actually be intercepted. So the mark arrives on the copy
236
- // we're about to throw away. Move it across before dropping.
237
- if (event.override) {
238
- this.applyOverrideMark(recent.id, event.override);
239
- }
240
- return;
241
- }
242
- this.recentRequests.set(requestKey, {
243
- at: now,
244
- id: request.id
245
- });
246
-
247
- // Clean up old entries to prevent memory leak. Future-dated entries are
248
- // stale by definition (clock moved backwards) — drop those too, or they
249
- // survive every sweep until the clock overtakes them.
250
- if (this.recentRequests.size > 100) {
251
- const cutoff = now - 5000;
252
- for (const [key, entry] of this.recentRequests.entries()) {
253
- if (entry.at < cutoff || entry.at > now) {
254
- this.recentRequests.delete(key);
255
- }
256
- }
257
- }
258
-
259
207
  // Build full URL with query params if present
260
208
  const queryString = request.params ? `?${new URLSearchParams(request.params).toString()}` : "";
261
209
  const fullUrl = `${request.url}${queryString}`;
@@ -287,7 +235,8 @@ class NetworkEventStore extends _sharedUi.BaseEventStore {
287
235
  graphqlVariables,
288
236
  // Present when this request came straight through the XHR wrapper
289
237
  // (axios, raw XHR). For `fetch` it arrives later — see the dedup branch.
290
- override: event.override
238
+ override: event.override,
239
+ condition: event.condition
291
240
  };
292
241
  this.pendingRequests.set(request.id, networkEvent);
293
242
  this.events = [networkEvent, ...this.events].slice(0, this.maxEvents);
@@ -331,26 +280,6 @@ class NetworkEventStore extends _sharedUi.BaseEventStore {
331
280
  }
332
281
  }
333
282
  }
334
-
335
- /**
336
- * Stamp an override mark onto an already-recorded event.
337
- *
338
- * Only ever moves a mark from a dropped duplicate onto the event that was
339
- * kept, so it never overwrites one that is already there.
340
- */
341
- applyOverrideMark(id, override) {
342
- const index = this.events.findIndex(e => e.id === id);
343
- if (index === -1 || this.events[index].override) return;
344
- const updatedEvent = {
345
- ...this.events[index],
346
- override
347
- };
348
- this.events = this.events.map((e, i) => i === index ? updatedEvent : e);
349
- const pending = this.pendingRequests.get(id);
350
- if (pending) this.pendingRequests.set(id, updatedEvent);
351
- this.notify(updatedEvent);
352
- this.notifyArrayListeners();
353
- }
354
283
  extractHost(url) {
355
284
  try {
356
285
  const urlObj = new URL(url);
@@ -388,7 +317,6 @@ class NetworkEventStore extends _sharedUi.BaseEventStore {
388
317
  clearEvents() {
389
318
  super.clearEvents();
390
319
  this.pendingRequests.clear();
391
- this.recentRequests.clear();
392
320
  }
393
321
 
394
322
  /**