@buoy-gg/network 7.0.41 → 7.0.42

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 (272) hide show
  1. package/README.md +11 -85
  2. package/lib/commonjs/index.js +1 -332
  3. package/lib/commonjs/index.web.js +1 -420
  4. package/lib/commonjs/network/components/DataViewer.web.js +1 -879
  5. package/lib/commonjs/network/components/NetworkCopyScreen.web.js +1 -48
  6. package/lib/commonjs/network/components/NetworkCopySettingsView.js +27 -755
  7. package/lib/commonjs/network/components/NetworkCopySettingsView.web.js +14 -1242
  8. package/lib/commonjs/network/components/NetworkDetailScreen.web.js +1 -101
  9. package/lib/commonjs/network/components/NetworkDetailStepper.js +1 -108
  10. package/lib/commonjs/network/components/NetworkDetailStepper.web.js +1 -298
  11. package/lib/commonjs/network/components/NetworkEventDetailView.js +15 -1130
  12. package/lib/commonjs/network/components/NetworkEventDetailView.web.js +15 -1484
  13. package/lib/commonjs/network/components/NetworkEventItemCompact.js +2 -401
  14. package/lib/commonjs/network/components/NetworkEventItemCompact.web.js +2 -820
  15. package/lib/commonjs/network/components/NetworkFilterScreen.web.js +1 -55
  16. package/lib/commonjs/network/components/NetworkFilterViewV3.js +1 -442
  17. package/lib/commonjs/network/components/NetworkFilterViewV3.web.js +1 -1310
  18. package/lib/commonjs/network/components/NetworkHeaderLive.js +1 -375
  19. package/lib/commonjs/network/components/NetworkHeaderLive.web.js +1 -623
  20. package/lib/commonjs/network/components/NetworkHeaderMenu.js +1 -190
  21. package/lib/commonjs/network/components/NetworkListScreen.js +1 -403
  22. package/lib/commonjs/network/components/NetworkListScreen.web.js +1 -624
  23. package/lib/commonjs/network/components/NetworkModal.js +1 -786
  24. package/lib/commonjs/network/components/NetworkNumberPrompt.js +1 -178
  25. package/lib/commonjs/network/components/NetworkNumberPrompt.web.js +1 -232
  26. package/lib/commonjs/network/components/NetworkOverrideEditor.js +1 -814
  27. package/lib/commonjs/network/components/NetworkOverrideEditor.web.js +1 -942
  28. package/lib/commonjs/network/components/NetworkOverrideHeaderButton.js +1 -107
  29. package/lib/commonjs/network/components/NetworkOverrideHeaderButton.web.js +1 -133
  30. package/lib/commonjs/network/components/NetworkOverridesButton.js +1 -90
  31. package/lib/commonjs/network/components/NetworkOverridesButton.web.js +1 -143
  32. package/lib/commonjs/network/components/NetworkOverridesScreen.js +1 -543
  33. package/lib/commonjs/network/components/NetworkOverridesScreen.web.js +1 -830
  34. package/lib/commonjs/network/components/NetworkSavedScreen.js +1 -226
  35. package/lib/commonjs/network/components/NetworkSavedScreen.web.js +1 -463
  36. package/lib/commonjs/network/components/NetworkUpgradeBanner.js +1 -76
  37. package/lib/commonjs/network/components/response-editor/Explorer.js +1 -973
  38. package/lib/commonjs/network/components/response-editor/Explorer.web.js +1 -1136
  39. package/lib/commonjs/network/components/response-editor/deleteNestedDataByPath.js +1 -56
  40. package/lib/commonjs/network/components/response-editor/editorContext.js +1 -33
  41. package/lib/commonjs/network/components/response-editor/updateNestedDataByPath.js +1 -61
  42. package/lib/commonjs/network/conditions/NetworkConditionsControl.js +1 -235
  43. package/lib/commonjs/network/conditions/NetworkConditionsControl.test.js +1 -136
  44. package/lib/commonjs/network/conditions/NetworkConditionsIcon.js +1 -33
  45. package/lib/commonjs/network/conditions/NetworkFloatingTool.js +1 -310
  46. package/lib/commonjs/network/conditions/NetworkFloatingTool.test.js +1 -219
  47. package/lib/commonjs/network/conditions/store.js +1 -75
  48. package/lib/commonjs/network/conditions/wait.js +1 -43
  49. package/lib/commonjs/network/conditions/xhr.js +1 -135
  50. package/lib/commonjs/network/hooks/useLiveNetworkEvent.js +1 -62
  51. package/lib/commonjs/network/hooks/useNetworkBadgeCounts.js +1 -83
  52. package/lib/commonjs/network/hooks/useNetworkEventList.js +1 -108
  53. package/lib/commonjs/network/hooks/useNetworkEvents.js +1 -318
  54. package/lib/commonjs/network/hooks/useNetworkHasMatches.js +1 -56
  55. package/lib/commonjs/network/hooks/useNetworkSaved.js +1 -38
  56. package/lib/commonjs/network/hooks/useNetworkSelection.js +1 -53
  57. package/lib/commonjs/network/hooks/useOverrideRules.js +1 -36
  58. package/lib/commonjs/network/hooks/useTickEveryMinute.js +1 -21
  59. package/lib/commonjs/network/overrides/applyToFetch.js +1 -80
  60. package/lib/commonjs/network/overrides/applyToXhr.js +2 -510
  61. package/lib/commonjs/network/overrides/engine.js +1 -93
  62. package/lib/commonjs/network/overrides/matchRule.js +1 -87
  63. package/lib/commonjs/network/overrides/overriddenRows.js +1 -37
  64. package/lib/commonjs/network/overrides/overrideRulesStore.js +1 -550
  65. package/lib/commonjs/network/overrides/presets.js +1 -70
  66. package/lib/commonjs/network/overrides/resolveOverride.js +1 -158
  67. package/lib/commonjs/network/overrides/types.js +1 -90
  68. package/lib/commonjs/network/sync/networkBodyResolver.js +1 -39
  69. package/lib/commonjs/network/sync/networkSyncAdapter.js +1 -882
  70. package/lib/commonjs/network/sync/overrideHonesty.release.test.js +1 -36
  71. package/lib/commonjs/network/types/index.js +1 -5
  72. package/lib/commonjs/network/utils/extractOperationName.js +1 -80
  73. package/lib/commonjs/network/utils/filterNetworkEvents.js +1 -96
  74. package/lib/commonjs/network/utils/formatGraphQLVariables.js +1 -219
  75. package/lib/commonjs/network/utils/formatting.js +1 -30
  76. package/lib/commonjs/network/utils/generateCurl.js +2 -71
  77. package/lib/commonjs/network/utils/generateNetworkCopyText.js +37 -166
  78. package/lib/commonjs/network/utils/httpCompletion.js +1 -37
  79. package/lib/commonjs/network/utils/networkEventStore.js +1 -411
  80. package/lib/commonjs/network/utils/networkListener.js +4 -1352
  81. package/lib/commonjs/network/utils/networkSavedStore.js +3 -577
  82. package/lib/commonjs/network/utils/networkSelectionStore.js +1 -193
  83. package/lib/commonjs/network/utils/realNow.js +1 -0
  84. package/lib/commonjs/network/utils/requestId.js +1 -16
  85. package/lib/commonjs/network/utils/resolveRequestUrl.js +1 -15
  86. package/lib/commonjs/network/utils/selectSavedEvents.js +1 -39
  87. package/lib/commonjs/network/utils/startNetworkCapture.js +1 -24
  88. package/lib/commonjs/network/utils/withPinnedFirst.js +1 -32
  89. package/lib/commonjs/preset.js +1 -93
  90. package/lib/module/index.js +1 -104
  91. package/lib/module/index.web.js +1 -82
  92. package/lib/module/network/components/DataViewer.web.js +1 -875
  93. package/lib/module/network/components/NetworkCopyScreen.web.js +1 -44
  94. package/lib/module/network/components/NetworkCopySettingsView.js +27 -750
  95. package/lib/module/network/components/NetworkCopySettingsView.web.js +14 -1237
  96. package/lib/module/network/components/NetworkDetailScreen.web.js +1 -97
  97. package/lib/module/network/components/NetworkDetailStepper.js +1 -104
  98. package/lib/module/network/components/NetworkDetailStepper.web.js +1 -296
  99. package/lib/module/network/components/NetworkEventDetailView.js +15 -1129
  100. package/lib/module/network/components/NetworkEventDetailView.web.js +15 -1482
  101. package/lib/module/network/components/NetworkEventItemCompact.js +2 -398
  102. package/lib/module/network/components/NetworkEventItemCompact.web.js +2 -818
  103. package/lib/module/network/components/NetworkFilterScreen.web.js +1 -51
  104. package/lib/module/network/components/NetworkFilterViewV3.js +1 -438
  105. package/lib/module/network/components/NetworkFilterViewV3.web.js +1 -1306
  106. package/lib/module/network/components/NetworkHeaderLive.js +1 -366
  107. package/lib/module/network/components/NetworkHeaderLive.web.js +1 -617
  108. package/lib/module/network/components/NetworkHeaderMenu.js +1 -184
  109. package/lib/module/network/components/NetworkListScreen.js +1 -399
  110. package/lib/module/network/components/NetworkListScreen.web.js +1 -622
  111. package/lib/module/network/components/NetworkModal.js +1 -781
  112. package/lib/module/network/components/NetworkNumberPrompt.js +1 -174
  113. package/lib/module/network/components/NetworkNumberPrompt.web.js +1 -230
  114. package/lib/module/network/components/NetworkOverrideEditor.js +1 -811
  115. package/lib/module/network/components/NetworkOverrideEditor.web.js +1 -939
  116. package/lib/module/network/components/NetworkOverrideHeaderButton.js +1 -104
  117. package/lib/module/network/components/NetworkOverrideHeaderButton.web.js +1 -131
  118. package/lib/module/network/components/NetworkOverridesButton.js +1 -87
  119. package/lib/module/network/components/NetworkOverridesButton.web.js +1 -141
  120. package/lib/module/network/components/NetworkOverridesScreen.js +1 -539
  121. package/lib/module/network/components/NetworkOverridesScreen.web.js +1 -826
  122. package/lib/module/network/components/NetworkSavedScreen.js +1 -222
  123. package/lib/module/network/components/NetworkSavedScreen.web.js +1 -461
  124. package/lib/module/network/components/NetworkUpgradeBanner.js +1 -72
  125. package/lib/module/network/components/response-editor/Explorer.js +1 -972
  126. package/lib/module/network/components/response-editor/Explorer.web.js +1 -1133
  127. package/lib/module/network/components/response-editor/deleteNestedDataByPath.js +1 -51
  128. package/lib/module/network/components/response-editor/editorContext.js +1 -29
  129. package/lib/module/network/components/response-editor/updateNestedDataByPath.js +1 -56
  130. package/lib/module/network/conditions/NetworkConditionsControl.js +1 -231
  131. package/lib/module/network/conditions/NetworkConditionsControl.test.js +1 -135
  132. package/lib/module/network/conditions/NetworkConditionsIcon.js +1 -29
  133. package/lib/module/network/conditions/NetworkFloatingTool.js +1 -304
  134. package/lib/module/network/conditions/NetworkFloatingTool.test.js +1 -218
  135. package/lib/module/network/conditions/store.js +1 -65
  136. package/lib/module/network/conditions/wait.js +1 -38
  137. package/lib/module/network/conditions/xhr.js +1 -131
  138. package/lib/module/network/hooks/useLiveNetworkEvent.js +1 -58
  139. package/lib/module/network/hooks/useNetworkBadgeCounts.js +1 -79
  140. package/lib/module/network/hooks/useNetworkEventList.js +1 -104
  141. package/lib/module/network/hooks/useNetworkEvents.js +1 -314
  142. package/lib/module/network/hooks/useNetworkHasMatches.js +1 -52
  143. package/lib/module/network/hooks/useNetworkSaved.js +1 -34
  144. package/lib/module/network/hooks/useNetworkSelection.js +1 -46
  145. package/lib/module/network/hooks/useOverrideRules.js +1 -32
  146. package/lib/module/network/hooks/useTickEveryMinute.js +1 -11
  147. package/lib/module/network/overrides/applyToFetch.js +1 -72
  148. package/lib/module/network/overrides/applyToXhr.js +2 -502
  149. package/lib/module/network/overrides/engine.js +1 -85
  150. package/lib/module/network/overrides/matchRule.js +1 -81
  151. package/lib/module/network/overrides/overriddenRows.js +1 -33
  152. package/lib/module/network/overrides/overrideRulesStore.js +1 -547
  153. package/lib/module/network/overrides/presets.js +1 -65
  154. package/lib/module/network/overrides/resolveOverride.js +1 -152
  155. package/lib/module/network/overrides/types.js +1 -86
  156. package/lib/module/network/sync/networkBodyResolver.js +1 -34
  157. package/lib/module/network/sync/networkSyncAdapter.js +1 -880
  158. package/lib/module/network/sync/overrideHonesty.release.test.js +1 -35
  159. package/lib/module/network/types/index.js +1 -3
  160. package/lib/module/network/utils/extractOperationName.js +1 -76
  161. package/lib/module/network/utils/filterNetworkEvents.js +1 -92
  162. package/lib/module/network/utils/formatGraphQLVariables.js +1 -213
  163. package/lib/module/network/utils/formatting.js +1 -9
  164. package/lib/module/network/utils/generateCurl.js +2 -67
  165. package/lib/module/network/utils/generateNetworkCopyText.js +37 -162
  166. package/lib/module/network/utils/httpCompletion.js +1 -32
  167. package/lib/module/network/utils/networkEventStore.js +1 -409
  168. package/lib/module/network/utils/networkListener.js +4 -1341
  169. package/lib/module/network/utils/networkSavedStore.js +3 -572
  170. package/lib/module/network/utils/networkSelectionStore.js +1 -188
  171. package/lib/module/network/utils/realNow.js +1 -0
  172. package/lib/module/network/utils/requestId.js +1 -12
  173. package/lib/module/network/utils/resolveRequestUrl.js +1 -11
  174. package/lib/module/network/utils/selectSavedEvents.js +1 -35
  175. package/lib/module/network/utils/startNetworkCapture.js +1 -21
  176. package/lib/module/network/utils/withPinnedFirst.js +1 -28
  177. package/lib/module/preset.js +1 -91
  178. package/lib/typescript/network/overrides/engine.d.ts +1 -1
  179. package/lib/typescript/network/overrides/matchRule.d.ts +1 -1
  180. package/lib/typescript/network/overrides/resolveOverride.d.ts +2 -2
  181. package/lib/typescript/network/overrides/types.d.ts +9 -0
  182. package/lib/typescript/network/utils/networkListener.d.ts +11 -0
  183. package/lib/typescript/network/utils/realNow.d.ts +18 -0
  184. package/lib/web/index.mjs +73 -74
  185. package/package.json +2 -2
  186. package/lib/typescript/index.d.ts.map +0 -1
  187. package/lib/typescript/index.web.d.ts.map +0 -1
  188. package/lib/typescript/network/components/DataViewer.web.d.ts.map +0 -1
  189. package/lib/typescript/network/components/NetworkCopyScreen.web.d.ts.map +0 -1
  190. package/lib/typescript/network/components/NetworkCopySettingsView.d.ts.map +0 -1
  191. package/lib/typescript/network/components/NetworkCopySettingsView.web.d.ts.map +0 -1
  192. package/lib/typescript/network/components/NetworkDetailScreen.web.d.ts.map +0 -1
  193. package/lib/typescript/network/components/NetworkDetailStepper.d.ts.map +0 -1
  194. package/lib/typescript/network/components/NetworkDetailStepper.web.d.ts.map +0 -1
  195. package/lib/typescript/network/components/NetworkEventDetailView.d.ts.map +0 -1
  196. package/lib/typescript/network/components/NetworkEventDetailView.web.d.ts.map +0 -1
  197. package/lib/typescript/network/components/NetworkEventItemCompact.d.ts.map +0 -1
  198. package/lib/typescript/network/components/NetworkEventItemCompact.web.d.ts.map +0 -1
  199. package/lib/typescript/network/components/NetworkFilterScreen.web.d.ts.map +0 -1
  200. package/lib/typescript/network/components/NetworkFilterViewV3.d.ts.map +0 -1
  201. package/lib/typescript/network/components/NetworkFilterViewV3.web.d.ts.map +0 -1
  202. package/lib/typescript/network/components/NetworkHeaderLive.d.ts.map +0 -1
  203. package/lib/typescript/network/components/NetworkHeaderLive.web.d.ts.map +0 -1
  204. package/lib/typescript/network/components/NetworkHeaderMenu.d.ts.map +0 -1
  205. package/lib/typescript/network/components/NetworkListScreen.d.ts.map +0 -1
  206. package/lib/typescript/network/components/NetworkListScreen.web.d.ts.map +0 -1
  207. package/lib/typescript/network/components/NetworkModal.d.ts.map +0 -1
  208. package/lib/typescript/network/components/NetworkNumberPrompt.d.ts.map +0 -1
  209. package/lib/typescript/network/components/NetworkNumberPrompt.web.d.ts.map +0 -1
  210. package/lib/typescript/network/components/NetworkOverrideEditor.d.ts.map +0 -1
  211. package/lib/typescript/network/components/NetworkOverrideEditor.web.d.ts.map +0 -1
  212. package/lib/typescript/network/components/NetworkOverrideHeaderButton.d.ts.map +0 -1
  213. package/lib/typescript/network/components/NetworkOverrideHeaderButton.web.d.ts.map +0 -1
  214. package/lib/typescript/network/components/NetworkOverridesButton.d.ts.map +0 -1
  215. package/lib/typescript/network/components/NetworkOverridesButton.web.d.ts.map +0 -1
  216. package/lib/typescript/network/components/NetworkOverridesScreen.d.ts.map +0 -1
  217. package/lib/typescript/network/components/NetworkOverridesScreen.web.d.ts.map +0 -1
  218. package/lib/typescript/network/components/NetworkSavedScreen.d.ts.map +0 -1
  219. package/lib/typescript/network/components/NetworkSavedScreen.web.d.ts.map +0 -1
  220. package/lib/typescript/network/components/NetworkUpgradeBanner.d.ts.map +0 -1
  221. package/lib/typescript/network/components/response-editor/Explorer.d.ts.map +0 -1
  222. package/lib/typescript/network/components/response-editor/Explorer.web.d.ts.map +0 -1
  223. package/lib/typescript/network/components/response-editor/deleteNestedDataByPath.d.ts.map +0 -1
  224. package/lib/typescript/network/components/response-editor/editorContext.d.ts.map +0 -1
  225. package/lib/typescript/network/components/response-editor/updateNestedDataByPath.d.ts.map +0 -1
  226. package/lib/typescript/network/conditions/NetworkConditionsControl.d.ts.map +0 -1
  227. package/lib/typescript/network/conditions/NetworkConditionsControl.test.d.ts.map +0 -1
  228. package/lib/typescript/network/conditions/NetworkConditionsIcon.d.ts.map +0 -1
  229. package/lib/typescript/network/conditions/NetworkFloatingTool.d.ts.map +0 -1
  230. package/lib/typescript/network/conditions/NetworkFloatingTool.test.d.ts.map +0 -1
  231. package/lib/typescript/network/conditions/store.d.ts.map +0 -1
  232. package/lib/typescript/network/conditions/wait.d.ts.map +0 -1
  233. package/lib/typescript/network/conditions/xhr.d.ts.map +0 -1
  234. package/lib/typescript/network/hooks/useLiveNetworkEvent.d.ts.map +0 -1
  235. package/lib/typescript/network/hooks/useNetworkBadgeCounts.d.ts.map +0 -1
  236. package/lib/typescript/network/hooks/useNetworkEventList.d.ts.map +0 -1
  237. package/lib/typescript/network/hooks/useNetworkEvents.d.ts.map +0 -1
  238. package/lib/typescript/network/hooks/useNetworkHasMatches.d.ts.map +0 -1
  239. package/lib/typescript/network/hooks/useNetworkSaved.d.ts.map +0 -1
  240. package/lib/typescript/network/hooks/useNetworkSelection.d.ts.map +0 -1
  241. package/lib/typescript/network/hooks/useOverrideRules.d.ts.map +0 -1
  242. package/lib/typescript/network/hooks/useTickEveryMinute.d.ts.map +0 -1
  243. package/lib/typescript/network/overrides/applyToFetch.d.ts.map +0 -1
  244. package/lib/typescript/network/overrides/applyToXhr.d.ts.map +0 -1
  245. package/lib/typescript/network/overrides/engine.d.ts.map +0 -1
  246. package/lib/typescript/network/overrides/matchRule.d.ts.map +0 -1
  247. package/lib/typescript/network/overrides/overriddenRows.d.ts.map +0 -1
  248. package/lib/typescript/network/overrides/overrideRulesStore.d.ts.map +0 -1
  249. package/lib/typescript/network/overrides/presets.d.ts.map +0 -1
  250. package/lib/typescript/network/overrides/resolveOverride.d.ts.map +0 -1
  251. package/lib/typescript/network/overrides/types.d.ts.map +0 -1
  252. package/lib/typescript/network/sync/networkBodyResolver.d.ts.map +0 -1
  253. package/lib/typescript/network/sync/networkSyncAdapter.d.ts.map +0 -1
  254. package/lib/typescript/network/sync/overrideHonesty.release.test.d.ts.map +0 -1
  255. package/lib/typescript/network/types/index.d.ts.map +0 -1
  256. package/lib/typescript/network/utils/extractOperationName.d.ts.map +0 -1
  257. package/lib/typescript/network/utils/filterNetworkEvents.d.ts.map +0 -1
  258. package/lib/typescript/network/utils/formatGraphQLVariables.d.ts.map +0 -1
  259. package/lib/typescript/network/utils/formatting.d.ts.map +0 -1
  260. package/lib/typescript/network/utils/generateCurl.d.ts.map +0 -1
  261. package/lib/typescript/network/utils/generateNetworkCopyText.d.ts.map +0 -1
  262. package/lib/typescript/network/utils/httpCompletion.d.ts.map +0 -1
  263. package/lib/typescript/network/utils/networkEventStore.d.ts.map +0 -1
  264. package/lib/typescript/network/utils/networkListener.d.ts.map +0 -1
  265. package/lib/typescript/network/utils/networkSavedStore.d.ts.map +0 -1
  266. package/lib/typescript/network/utils/networkSelectionStore.d.ts.map +0 -1
  267. package/lib/typescript/network/utils/requestId.d.ts.map +0 -1
  268. package/lib/typescript/network/utils/resolveRequestUrl.d.ts.map +0 -1
  269. package/lib/typescript/network/utils/selectSavedEvents.d.ts.map +0 -1
  270. package/lib/typescript/network/utils/startNetworkCapture.d.ts.map +0 -1
  271. package/lib/typescript/network/utils/withPinnedFirst.d.ts.map +0 -1
  272. package/lib/typescript/preset.d.ts.map +0 -1
@@ -1,574 +1,5 @@
1
- "use strict";
1
+ "use strict";import{devToolsStorageKeys as l,persistentStorage as h}from"@buoy-gg/shared-ui";import{TIER_LIMITS as I}from"@buoy-gg/shared-ui";export const SESSION_ID=`${Date.now().toString(36)}_${Math.random().toString(36).slice(2,8)}`;export const MAX_PINNED=25;export const MAX_SAVED=50;export const FREE_TIER_SAVED_LIMIT=I["network.saved"].free;const d=16*1024;const f=512*1024;function v(n){if(!n||typeof n!=="object")return false;const e=n;return typeof e.id==="string"&&typeof e.url==="string"}const S=800;export function flagsForEventId(n,e){if(!e)return{pinned:false,saved:false};const t=n.pinnedLiveIds.has(e);const s=n.savedLiveIds.has(e);if(t||s)return{pinned:t,saved:s};const o=n.records.find(r=>r.event.id===e);return o?{pinned:o.pinned,saved:o.saved}:{pinned:false,saved:false}}const k={records:[],pinnedEvents:[],savedRecords:[],pinnedLiveIds:new Set,savedLiveIds:new Set};function p(n){if(n===void 0||n===null)return{value:n,truncated:false};if(typeof n==="string"){if(n.length<=d)return{value:n,truncated:false};return{value:`${n.slice(0,d)}
2
2
 
3
- /**
4
- * Pinned + saved network requests.
5
- *
6
- * The live `networkEventStore` is volatile by design: it caps at 500 events
7
- * and `clearEvents()` wipes it. So "pin this failure so I don't lose it"
8
- * cannot be an ID pointing into that store — it has to be a SNAPSHOT that
9
- * outlives the stream. This store owns those snapshots and persists them.
10
- *
11
- * Two independent flags per record:
12
- * - `pinned` — hoisted into the PINNED section at the top of the list,
13
- * unaffected by search/filters.
14
- * - `saved` — kept in the Saved (favorites) screen, out of the live stream.
15
- * A record exists while either flag is true and is dropped when both go false,
16
- * so all four states fall out without special cases.
17
- *
18
- * Request IDs include a runtime session and a counter. Saved records also
19
- * track their owning session because live rows disappear on app reload.
20
- * Records use a generated `key`, carry the `sessionId` that captured them,
21
- * and only trust `liveId` while that
22
- * session is the current one. Snapshots restored from an earlier session get
23
- * their event `id` rewritten to `saved:<key>` so a restored pin can never
24
- * resolve to (or dedupe against) an unrelated live request.
25
- *
26
- * Shape/persistence follows `ignoredPatternsStore` in @buoy-gg/shared-ui:
27
- * lazy hydrate on first subscribe, a `dirty` flag so a slow read can't clobber
28
- * a mutation, and commit = set + emit + persist.
29
- */
3
+ \u2026 truncated by Buoy (body exceeded ${d} bytes)`,truncated:true}}try{const e=JSON.stringify(n);if(e===void 0)return{value:void 0,truncated:false};if(e.length<=d)return{value:n,truncated:false};return{value:`${e.slice(0,d)}
30
4
 
31
- import { devToolsStorageKeys, persistentStorage } from "@buoy-gg/shared-ui";
32
- import { TIER_LIMITS } from "@buoy-gg/shared-ui";
33
- /** Identifies THIS runtime. Records from other sessions can't trust `liveId`. */
34
- export const SESSION_ID = `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
35
-
36
- /** Pins render un-virtualized in the list header, so the count stays bounded. */
37
- export const MAX_PINNED = 25;
38
- /** Upper bound on the persisted favorites list (oldest unpinned evicted). */
39
- export const MAX_SAVED = 50;
40
- /**
41
- * @deprecated Read the live limit with `useLimit("network.saved")` — this only
42
- * reports the FREE tier's value and ignores the Weekend Pass. Kept because it
43
- * is part of the package's public exports.
44
- */
45
- export const FREE_TIER_SAVED_LIMIT = TIER_LIMITS["network.saved"].free;
46
- /** Per-body cap on a snapshot. */
47
- const BODY_LIMIT = 16 * 1024;
48
- /**
49
- * Total body bytes kept across ALL records. `persistentStorage` falls back to
50
- * AsyncStorage on RN CLI (no expo-file-system), which has real per-key size
51
- * limits, so the blob is budgeted rather than left to grow. Bodies are dropped
52
- * oldest-first, pins last.
53
- */
54
- const TOTAL_BODY_BUDGET = 512 * 1024;
55
-
56
- /** Why a toggle didn't take effect, so the UI can explain itself. */
57
-
58
- /**
59
- * Is this actually a network event we can store and re-render later?
60
- *
61
- * This store WRITES TO DISK, which turns "accept whatever you're handed" into a
62
- * permanent failure: a bad value becomes a record, the record is persisted, and
63
- * every subsequent render of the pinned strip throws on it — a crash loop that
64
- * survives restarts. Found exactly that way, by an automation harness invoking
65
- * a row's `onLongPress` with a gesture object instead of the row's event
66
- * (`TypeError: Cannot read property 'replace' of undefined` in the row, from
67
- * `event.url.replace(...)`).
68
- *
69
- * `id` and `url` are the two fields the row and detail views dereference
70
- * without guarding, so they are the bar for admission — both here, where
71
- * records are created, and in `parseRecords`, so anything already on disk is
72
- * dropped at hydration instead of crashing forever.
73
- */
74
- function isStorableEvent(value) {
75
- if (!value || typeof value !== "object") return false;
76
- const event = value;
77
- return typeof event.id === "string" && typeof event.url === "string";
78
- }
79
-
80
- /**
81
- * Handlers the desktop dashboard installs to forward mutations to the device.
82
- *
83
- * The dashboard must not keep its own copy of the device's pins: the device is
84
- * the only place the snapshots can stay in sync with the traffic that produced
85
- * them (and the only place a >16KB body still exists). So in remote mode this
86
- * store becomes a MIRROR — same seam `networkEventStore.disableCapture()` +
87
- * `replaceEvents()` uses for the event list.
88
- */
89
-
90
- /**
91
- * How long a local optimistic write suppresses incoming mirrors.
92
- *
93
- * A toggle is applied locally AND forwarded; the device echoes the new state
94
- * back on its next snapshot. Without this window the in-flight snapshot — the
95
- * one already on the wire when the user tapped — would land first and visibly
96
- * revert the toggle for a frame.
97
- */
98
- const REMOTE_ECHO_GRACE_MS = 800;
99
-
100
- /**
101
- * Read an event's flags out of a state snapshot.
102
- *
103
- * Two lookups because an event ID means two different things: a live request
104
- * matches by `liveId` (the fast Set path), while a snapshot restored from an
105
- * earlier session carries the namespaced `saved:<key>` id and has to be found
106
- * among the records.
107
- */
108
- export function flagsForEventId(state, eventId) {
109
- if (!eventId) return {
110
- pinned: false,
111
- saved: false
112
- };
113
- const pinned = state.pinnedLiveIds.has(eventId);
114
- const saved = state.savedLiveIds.has(eventId);
115
- if (pinned || saved) return {
116
- pinned,
117
- saved
118
- };
119
- const record = state.records.find(entry => entry.event.id === eventId);
120
- return record ? {
121
- pinned: record.pinned,
122
- saved: record.saved
123
- } : {
124
- pinned: false,
125
- saved: false
126
- };
127
- }
128
- const EMPTY_STATE = {
129
- records: [],
130
- pinnedEvents: [],
131
- savedRecords: [],
132
- pinnedLiveIds: new Set(),
133
- savedLiveIds: new Set()
134
- };
135
- function truncateBody(data) {
136
- if (data === undefined || data === null) return {
137
- value: data,
138
- truncated: false
139
- };
140
- if (typeof data === "string") {
141
- if (data.length <= BODY_LIMIT) return {
142
- value: data,
143
- truncated: false
144
- };
145
- return {
146
- value: `${data.slice(0, BODY_LIMIT)}\n\n… truncated by Buoy (body exceeded ${BODY_LIMIT} bytes)`,
147
- truncated: true
148
- };
149
- }
150
- try {
151
- const serialized = JSON.stringify(data);
152
- if (serialized === undefined) return {
153
- value: undefined,
154
- truncated: false
155
- };
156
- if (serialized.length <= BODY_LIMIT) return {
157
- value: data,
158
- truncated: false
159
- };
160
- return {
161
- value: `${serialized.slice(0, BODY_LIMIT)}\n\n… truncated by Buoy (body exceeded ${BODY_LIMIT} bytes)`,
162
- truncated: true
163
- };
164
- } catch {
165
- // Circular or otherwise unserializable — it can't be persisted anyway.
166
- return {
167
- value: "[unserializable body]",
168
- truncated: true
169
- };
170
- }
171
- }
172
-
173
- /** Freeze an event into a persistable snapshot with capped bodies. */
174
- function snapshot(event) {
175
- const request = truncateBody(event.requestData);
176
- const response = truncateBody(event.responseData);
177
- if (!request.truncated && !response.truncated) {
178
- return {
179
- event: {
180
- ...event
181
- },
182
- bodyTruncated: false
183
- };
184
- }
185
- return {
186
- event: {
187
- ...event,
188
- requestData: request.value,
189
- responseData: response.value
190
- },
191
- bodyTruncated: true
192
- };
193
- }
194
-
195
- /**
196
- * Bytes a snapshot's bodies actually occupy. Sizes are clamped to BODY_LIMIT
197
- * because `snapshot()` already truncated anything larger, and a stripped body
198
- * counts as zero — otherwise the budget would never converge.
199
- */
200
- function bodyBytes(event) {
201
- let total = 0;
202
- if (event.requestData != null) {
203
- total += Math.min(event.requestSize ?? 0, BODY_LIMIT);
204
- }
205
- if (event.responseData != null) {
206
- total += Math.min(event.responseSize ?? 0, BODY_LIMIT);
207
- }
208
- return total;
209
- }
210
-
211
- /**
212
- * Enforce TOTAL_BODY_BUDGET by stripping bodies from the least important
213
- * records first (oldest, unpinned). The record itself — URL, status, headers,
214
- * timings, the error — always survives; only the payload goes.
215
- */
216
- function applyBodyBudget(records) {
217
- let total = 0;
218
- for (const record of records) total += bodyBytes(record.event);
219
- if (total <= TOTAL_BODY_BUDGET) return records;
220
-
221
- // Keep bodies for pins first, then newest saves.
222
- const priority = [...records].sort((a, b) => {
223
- if (a.pinned !== b.pinned) return a.pinned ? -1 : 1;
224
- return b.savedAt - a.savedAt;
225
- });
226
- const stripped = new Set();
227
- let kept = 0;
228
- for (const record of priority) {
229
- const size = bodyBytes(record.event);
230
- if (kept + size <= TOTAL_BODY_BUDGET) {
231
- kept += size;
232
- } else {
233
- stripped.add(record.key);
234
- }
235
- }
236
- if (stripped.size === 0) return records;
237
- return records.map(record => stripped.has(record.key) ? {
238
- ...record,
239
- event: {
240
- ...record.event,
241
- requestData: undefined,
242
- responseData: undefined
243
- },
244
- bodyTruncated: true
245
- } : record);
246
- }
247
-
248
- /** Tolerate junk/older shapes in storage rather than throwing away the list. */
249
- function parseRecords(raw) {
250
- if (!Array.isArray(raw)) return [];
251
- const parsed = [];
252
- for (const entry of raw) {
253
- if (!entry || typeof entry !== "object") continue;
254
- const record = entry;
255
- if (typeof record.key !== "string") continue;
256
- // Not just "is an object" — a record whose event can't be rendered would
257
- // crash the list on every launch, and storage is forever. Drop it instead.
258
- if (!isStorableEvent(record.event)) continue;
259
- if (!record.pinned && !record.saved) continue;
260
- parsed.push({
261
- key: record.key,
262
- // A restored snapshot's `event.id` is a stale per-runtime counter, so it
263
- // is rewritten to a namespaced id that can never collide with a live one.
264
- event: {
265
- ...record.event,
266
- id: `saved:${record.key}`
267
- },
268
- pinned: !!record.pinned,
269
- saved: !!record.saved,
270
- savedAt: typeof record.savedAt === "number" ? record.savedAt : 0,
271
- sessionId: typeof record.sessionId === "string" ? record.sessionId : "unknown",
272
- liveId: typeof record.liveId === "string" ? record.liveId : "",
273
- bodyTruncated: !!record.bodyTruncated
274
- });
275
- }
276
- return parsed;
277
- }
278
- class NetworkSavedStore {
279
- records = [];
280
- listeners = new Set();
281
- loaded = false;
282
- loadPromise = null;
283
- /** Set once a mutation happens so a slow initial load can't clobber it. */
284
- dirty = false;
285
- keyCounter = 0;
286
- /**
287
- * Live IDs tracked in THIS session. `syncFromLive` runs on every captured
288
- * response, so the hot path has to be a Set membership test.
289
- */
290
- trackedLiveIds = new Set();
291
- /** Derived view, rebuilt on commit so consumers get stable references. */
292
- state = EMPTY_STATE;
293
- /** Set on the desktop dashboard — see NetworkSavedRemote. */
294
- remote = null;
295
- remoteEchoUntil = 0;
296
- getState() {
297
- return this.state;
298
- }
299
-
300
- // ===========================================================================
301
- // REMOTE MIRROR (desktop dashboard)
302
- // ===========================================================================
303
-
304
- /**
305
- * Turn this store into a mirror of a connected device's records. Mutations
306
- * are applied locally for immediate feedback AND forwarded to the device,
307
- * which is the source of truth; nothing is persisted to this machine.
308
- */
309
- enableRemoteMode(remote) {
310
- this.remote = remote;
311
- }
312
- disableRemoteMode() {
313
- this.remote = null;
314
- }
315
-
316
- /**
317
- * Feed the device's records in (mirror mode only). `force` bypasses the
318
- * echo guard — used when tearing the mirror down, where there is no later
319
- * snapshot to converge on.
320
- */
321
- replaceRecords(records, force = false) {
322
- if (!this.remote) return;
323
- // A local toggle is still in flight — the snapshot on the wire predates it
324
- // and would flicker the button back. Drop this one; the next carries the
325
- // device's real answer.
326
- if (!force && Date.now() < this.remoteEchoUntil) return;
327
- this.records = records;
328
- this.rebuild();
329
- this.emit();
330
- }
331
-
332
- /**
333
- * Whether a record's `liveId` can be trusted against the current event
334
- * stream. Locally that means "captured in this runtime"; mirrored, the ids
335
- * belong to the device whose events we are also mirroring, so they line up
336
- * by construction.
337
- */
338
- sessionMatches(record) {
339
- return this.remote !== null || record.sessionId === SESSION_ID;
340
- }
341
- getRecords() {
342
- return this.records;
343
- }
344
- subscribe(listener) {
345
- this.listeners.add(listener);
346
- void this.ensureLoaded();
347
- return () => {
348
- this.listeners.delete(listener);
349
- };
350
- }
351
-
352
- /**
353
- * Resolve an event the live store no longer has — a pin whose request was
354
- * cleared or aged out, or a snapshot restored from an earlier session.
355
- */
356
- getEventById(id) {
357
- for (const record of this.records) {
358
- if (record.event.id === id) return record.event;
359
- }
360
- return null;
361
- }
362
- isPinned(liveId) {
363
- return this.state.pinnedLiveIds.has(liveId);
364
- }
365
- isSaved(liveId) {
366
- return this.state.savedLiveIds.has(liveId);
367
- }
368
-
369
- /**
370
- * Keep a tracked snapshot current while its request is still in flight —
371
- * pinning a pending request would otherwise freeze it as "Pending" forever.
372
- * Called from `networkEventStore` for every response/error, so the no-op
373
- * path (nothing pinned) is a single empty-Set check.
374
- */
375
- syncFromLive(event) {
376
- if (this.trackedLiveIds.size === 0) return;
377
- if (!this.trackedLiveIds.has(event.id)) return;
378
- const index = this.records.findIndex(record => this.sessionMatches(record) && record.liveId === event.id);
379
- if (index === -1) return;
380
- const next = snapshot(event);
381
- this.commit(this.records.map((record, i) => i === index ? {
382
- ...record,
383
- event: next.event,
384
- bodyTruncated: next.bodyTruncated
385
- } : record));
386
- }
387
- togglePin(event) {
388
- return this.setFlag(event, "pinned");
389
- }
390
- toggleSave(event) {
391
- return this.setFlag(event, "saved");
392
- }
393
-
394
- /** Drop a record outright, whatever its flags. */
395
- remove(key) {
396
- if (!this.records.some(record => record.key === key)) return;
397
- this.remote?.removeRecord(key);
398
- this.commit(this.records.filter(record => record.key !== key));
399
- }
400
-
401
- /** Empty the favorites list; pinned-only records stay pinned. */
402
- clearSaved() {
403
- if (!this.records.some(record => record.saved)) return;
404
- this.remote?.clearSaved();
405
- this.commit(this.records.filter(record => record.pinned).map(record => ({
406
- ...record,
407
- saved: false
408
- })));
409
- }
410
-
411
- /** Unpin everything; saved-only records stay saved. */
412
- clearPinned() {
413
- if (!this.records.some(record => record.pinned)) return;
414
- this.remote?.clearPinned();
415
- this.commit(this.records.filter(record => record.saved).map(record => ({
416
- ...record,
417
- pinned: false
418
- })));
419
- }
420
- setFlag(event, flag) {
421
- // Gate every write on a real event — see isStorableEvent. Rejecting here is
422
- // what keeps a bad caller from turning into persisted, crash-looping state.
423
- if (!isStorableEvent(event)) return {
424
- ok: false,
425
- reason: "invalid-event"
426
- };
427
- const existing = this.findRecordFor(event);
428
-
429
- // Mirror mode: tell the device first, then apply the same change locally so
430
- // the button responds now instead of a snapshot later.
431
- if (this.remote) {
432
- const next = existing ? !existing[flag] : true;
433
- if (flag === "pinned") this.remote.setPinned(event.id, next);else this.remote.setSaved(event.id, next);
434
- this.remoteEchoUntil = Date.now() + REMOTE_ECHO_GRACE_MS;
435
- }
436
- if (existing) {
437
- const next = !existing[flag];
438
- if (next && !this.hasRoomFor(flag, existing.key)) {
439
- return {
440
- ok: false,
441
- reason: flag === "pinned" ? "pin-cap" : "saved-cap"
442
- };
443
- }
444
- const updated = {
445
- ...existing,
446
- [flag]: next
447
- };
448
- this.commit(updated.pinned || updated.saved ? this.records.map(record => record.key === existing.key ? updated : record) : this.records.filter(record => record.key !== existing.key));
449
- return {
450
- ok: true,
451
- active: next
452
- };
453
- }
454
- if (!this.hasRoomFor(flag, null)) {
455
- return {
456
- ok: false,
457
- reason: flag === "pinned" ? "pin-cap" : "saved-cap"
458
- };
459
- }
460
- const savedAt = Date.now();
461
- const frozen = snapshot(event);
462
- const record = {
463
- key: `${SESSION_ID}_${++this.keyCounter}`,
464
- event: frozen.event,
465
- pinned: flag === "pinned",
466
- saved: flag === "saved",
467
- savedAt,
468
- sessionId: SESSION_ID,
469
- liveId: event.id,
470
- bodyTruncated: frozen.bodyTruncated
471
- };
472
- this.commit(this.evictIfNeeded([record, ...this.records]));
473
- return {
474
- ok: true,
475
- active: true
476
- };
477
- }
478
-
479
- /**
480
- * Match an event to its record: by live ID within this session, or by the
481
- * namespaced snapshot ID when the row being toggled IS a restored snapshot.
482
- */
483
- findRecordFor(event) {
484
- return this.records.find(record => record.event.id === event.id || this.sessionMatches(record) && record.liveId === event.id);
485
- }
486
- hasRoomFor(flag, exceptKey) {
487
- const limit = flag === "pinned" ? MAX_PINNED : MAX_SAVED;
488
- const count = this.records.filter(record => record[flag] && record.key !== exceptKey).length;
489
- return count < limit;
490
- }
491
-
492
- /** Trim the oldest UNPINNED saves once the list outgrows MAX_SAVED. */
493
- evictIfNeeded(records) {
494
- const saved = records.filter(record => record.saved);
495
- if (saved.length <= MAX_SAVED) return records;
496
- const evictable = saved.filter(record => !record.pinned).sort((a, b) => a.savedAt - b.savedAt).slice(0, saved.length - MAX_SAVED);
497
- if (evictable.length === 0) return records;
498
- const evicted = new Set(evictable.map(record => record.key));
499
- return records.filter(record => !evicted.has(record.key));
500
- }
501
- ensureLoaded() {
502
- // Mirror mode reads the device's records, never this machine's storage.
503
- if (this.remote) return Promise.resolve();
504
- if (this.loaded) return Promise.resolve();
505
- if (this.loadPromise) return this.loadPromise;
506
- this.loadPromise = (async () => {
507
- try {
508
- const stored = await persistentStorage.getItem(devToolsStorageKeys.network.saved());
509
- // A pin may have raced this read — never overwrite user intent.
510
- if (stored && !this.dirty) {
511
- this.records = parseRecords(JSON.parse(stored));
512
- this.rebuild();
513
- }
514
- } catch {
515
- // Corrupt or unreadable — start empty rather than crashing the tool.
516
- } finally {
517
- this.loaded = true;
518
- this.emit();
519
- }
520
- })();
521
- return this.loadPromise;
522
- }
523
- persist() {
524
- // The dashboard must not write the device's pins into its own storage —
525
- // they'd outlive the connection and shadow the next device.
526
- if (this.remote) return;
527
- persistentStorage.setItem(devToolsStorageKeys.network.saved(), JSON.stringify(this.records)).catch(() => {
528
- // Silently fail — records remain in memory for this session.
529
- });
530
- }
531
- commit(next) {
532
- this.dirty = true;
533
- // The budget is applied at COMMIT, not at persist, so what the UI shows is
534
- // exactly what survives a reload — no silently-vanishing bodies.
535
- this.records = applyBodyBudget(next);
536
- this.rebuild();
537
- this.emit();
538
- this.persist();
539
- }
540
-
541
- /** Rebuild the derived view once per mutation so renders get stable refs. */
542
- rebuild() {
543
- const pinnedRecords = [];
544
- const savedRecords = [];
545
- const pinnedLiveIds = new Set();
546
- const savedLiveIds = new Set();
547
- const tracked = new Set();
548
- for (const record of this.records) {
549
- if (record.pinned) pinnedRecords.push(record);
550
- if (record.saved) savedRecords.push(record);
551
- if (!this.sessionMatches(record) || !record.liveId) continue;
552
- tracked.add(record.liveId);
553
- if (record.pinned) pinnedLiveIds.add(record.liveId);
554
- if (record.saved) savedLiveIds.add(record.liveId);
555
- }
556
- this.trackedLiveIds = tracked;
557
- this.state = {
558
- records: this.records,
559
- pinnedEvents: pinnedRecords.map(record => record.event),
560
- savedRecords,
561
- pinnedLiveIds,
562
- savedLiveIds
563
- };
564
- }
565
- emit() {
566
- this.listeners.forEach(listener => listener());
567
- }
568
- }
569
-
570
- /**
571
- * Singleton — one instance per JS runtime, shared by the mobile modal, the
572
- * desktop inline port and the sync adapter.
573
- */
574
- export const networkSavedStore = new NetworkSavedStore();
5
+ \u2026 truncated by Buoy (body exceeded ${d} bytes)`,truncated:true}}catch{return{value:"[unserializable body]",truncated:true}}}function m(n){const e=p(n.requestData);const t=p(n.responseData);if(!e.truncated&&!t.truncated){return{event:{...n},bodyTruncated:false}}return{event:{...n,requestData:e.value,responseData:t.value},bodyTruncated:true}}function y(n){let e=0;if(n.requestData!=null){e+=Math.min(n.requestSize??0,d)}if(n.responseData!=null){e+=Math.min(n.responseSize??0,d)}return e}function b(n){let e=0;for(const r of n)e+=y(r.event);if(e<=f)return n;const t=[...n].sort((r,i)=>{if(r.pinned!==i.pinned)return r.pinned?-1:1;return i.savedAt-r.savedAt});const s=new Set;let o=0;for(const r of t){const i=y(r.event);if(o+i<=f){o+=i}else{s.add(r.key)}}if(s.size===0)return n;return n.map(r=>s.has(r.key)?{...r,event:{...r.event,requestData:void 0,responseData:void 0},bodyTruncated:true}:r)}function E(n){if(!Array.isArray(n))return[];const e=[];for(const t of n){if(!t||typeof t!=="object")continue;const s=t;if(typeof s.key!=="string")continue;if(!v(s.event))continue;if(!s.pinned&&!s.saved)continue;e.push({key:s.key,event:{...s.event,id:`saved:${s.key}`},pinned:!!s.pinned,saved:!!s.saved,savedAt:typeof s.savedAt==="number"?s.savedAt:0,sessionId:typeof s.sessionId==="string"?s.sessionId:"unknown",liveId:typeof s.liveId==="string"?s.liveId:"",bodyTruncated:!!s.bodyTruncated})}return e}class g{records=[];listeners=new Set;loaded=false;loadPromise=null;dirty=false;keyCounter=0;trackedLiveIds=new Set;state=k;remote=null;remoteEchoUntil=0;getState(){return this.state}enableRemoteMode(e){this.remote=e}disableRemoteMode(){this.remote=null}replaceRecords(e,t=false){if(!this.remote)return;if(!t&&Date.now()<this.remoteEchoUntil)return;this.records=e;this.rebuild();this.emit()}sessionMatches(e){return this.remote!==null||e.sessionId===SESSION_ID}getRecords(){return this.records}subscribe(e){this.listeners.add(e);void this.ensureLoaded();return()=>{this.listeners.delete(e)}}getEventById(e){for(const t of this.records){if(t.event.id===e)return t.event}return null}isPinned(e){return this.state.pinnedLiveIds.has(e)}isSaved(e){return this.state.savedLiveIds.has(e)}syncFromLive(e){if(this.trackedLiveIds.size===0)return;if(!this.trackedLiveIds.has(e.id))return;const t=this.records.findIndex(o=>this.sessionMatches(o)&&o.liveId===e.id);if(t===-1)return;const s=m(e);this.commit(this.records.map((o,r)=>r===t?{...o,event:s.event,bodyTruncated:s.bodyTruncated}:o))}togglePin(e){return this.setFlag(e,"pinned")}toggleSave(e){return this.setFlag(e,"saved")}remove(e){if(!this.records.some(t=>t.key===e))return;this.remote?.removeRecord(e);this.commit(this.records.filter(t=>t.key!==e))}clearSaved(){if(!this.records.some(e=>e.saved))return;this.remote?.clearSaved();this.commit(this.records.filter(e=>e.pinned).map(e=>({...e,saved:false})))}clearPinned(){if(!this.records.some(e=>e.pinned))return;this.remote?.clearPinned();this.commit(this.records.filter(e=>e.saved).map(e=>({...e,pinned:false})))}setFlag(e,t){if(!v(e))return{ok:false,reason:"invalid-event"};const s=this.findRecordFor(e);if(this.remote){const a=s?!s[t]:true;if(t==="pinned")this.remote.setPinned(e.id,a);else this.remote.setSaved(e.id,a);this.remoteEchoUntil=Date.now()+S}if(s){const a=!s[t];if(a&&!this.hasRoomFor(t,s.key)){return{ok:false,reason:t==="pinned"?"pin-cap":"saved-cap"}}const u={...s,[t]:a};this.commit(u.pinned||u.saved?this.records.map(c=>c.key===s.key?u:c):this.records.filter(c=>c.key!==s.key));return{ok:true,active:a}}if(!this.hasRoomFor(t,null)){return{ok:false,reason:t==="pinned"?"pin-cap":"saved-cap"}}const o=Date.now();const r=m(e);const i={key:`${SESSION_ID}_${++this.keyCounter}`,event:r.event,pinned:t==="pinned",saved:t==="saved",savedAt:o,sessionId:SESSION_ID,liveId:e.id,bodyTruncated:r.bodyTruncated};this.commit(this.evictIfNeeded([i,...this.records]));return{ok:true,active:true}}findRecordFor(e){return this.records.find(t=>t.event.id===e.id||this.sessionMatches(t)&&t.liveId===e.id)}hasRoomFor(e,t){const s=e==="pinned"?MAX_PINNED:MAX_SAVED;const o=this.records.filter(r=>r[e]&&r.key!==t).length;return o<s}evictIfNeeded(e){const t=e.filter(r=>r.saved);if(t.length<=MAX_SAVED)return e;const s=t.filter(r=>!r.pinned).sort((r,i)=>r.savedAt-i.savedAt).slice(0,t.length-MAX_SAVED);if(s.length===0)return e;const o=new Set(s.map(r=>r.key));return e.filter(r=>!o.has(r.key))}ensureLoaded(){if(this.remote)return Promise.resolve();if(this.loaded)return Promise.resolve();if(this.loadPromise)return this.loadPromise;this.loadPromise=(async()=>{try{const e=await h.getItem(l.network.saved());if(e&&!this.dirty){this.records=E(JSON.parse(e));this.rebuild()}}catch{}finally{this.loaded=true;this.emit()}})();return this.loadPromise}persist(){if(this.remote)return;h.setItem(l.network.saved(),JSON.stringify(this.records)).catch(()=>{})}commit(e){this.dirty=true;this.records=b(e);this.rebuild();this.emit();this.persist()}rebuild(){const e=[];const t=[];const s=new Set;const o=new Set;const r=new Set;for(const i of this.records){if(i.pinned)e.push(i);if(i.saved)t.push(i);if(!this.sessionMatches(i)||!i.liveId)continue;r.add(i.liveId);if(i.pinned)s.add(i.liveId);if(i.saved)o.add(i.liveId)}this.trackedLiveIds=r;this.state={records:this.records,pinnedEvents:e.map(i=>i.event),savedRecords:t,pinnedLiveIds:s,savedLiveIds:o}}emit(){this.listeners.forEach(e=>e())}}export const networkSavedStore=new g;