tempest-react-sdk 0.52.0 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (507) hide show
  1. package/README.md +58 -7
  2. package/dist/access/Can.cjs +1 -1
  3. package/dist/access/Can.cjs.map +1 -1
  4. package/dist/access/Can.js +3 -3
  5. package/dist/access/Can.js.map +1 -1
  6. package/dist/audio/audio-bus.cjs +2 -0
  7. package/dist/audio/audio-bus.cjs.map +1 -0
  8. package/dist/audio/audio-bus.js +116 -0
  9. package/dist/audio/audio-bus.js.map +1 -0
  10. package/dist/audio/level-meter.cjs +1 -1
  11. package/dist/audio/level-meter.cjs.map +1 -1
  12. package/dist/audio/level-meter.js +15 -15
  13. package/dist/audio/level-meter.js.map +1 -1
  14. package/dist/audio/noise-floor.cjs +2 -0
  15. package/dist/audio/noise-floor.cjs.map +1 -0
  16. package/dist/audio/noise-floor.js +27 -0
  17. package/dist/audio/noise-floor.js.map +1 -0
  18. package/dist/audio/use-audio-bus.cjs +2 -0
  19. package/dist/audio/use-audio-bus.cjs.map +1 -0
  20. package/dist/audio/use-audio-bus.js +11 -0
  21. package/dist/audio/use-audio-bus.js.map +1 -0
  22. package/dist/audio/voice-activity.cjs +2 -0
  23. package/dist/audio/voice-activity.cjs.map +1 -0
  24. package/dist/audio/voice-activity.js +26 -0
  25. package/dist/audio/voice-activity.js.map +1 -0
  26. package/dist/audio/voice-chain-stages.cjs +2 -0
  27. package/dist/audio/voice-chain-stages.cjs.map +1 -0
  28. package/dist/audio/voice-chain-stages.js +82 -0
  29. package/dist/audio/voice-chain-stages.js.map +1 -0
  30. package/dist/audio/voice-chain.cjs +2 -0
  31. package/dist/audio/voice-chain.cjs.map +1 -0
  32. package/dist/audio/voice-chain.js +74 -0
  33. package/dist/audio/voice-chain.js.map +1 -0
  34. package/dist/auth/lazy-with-retry.cjs.map +1 -1
  35. package/dist/auth/lazy-with-retry.js.map +1 -1
  36. package/dist/auth/passkey.cjs.map +1 -1
  37. package/dist/auth/passkey.js.map +1 -1
  38. package/dist/br/BrazilStateCitySelect.cjs +1 -1
  39. package/dist/br/BrazilStateCitySelect.cjs.map +1 -1
  40. package/dist/br/BrazilStateCitySelect.js +36 -28
  41. package/dist/br/BrazilStateCitySelect.js.map +1 -1
  42. package/dist/br/data/br-centroids.cjs +1 -1
  43. package/dist/br/data/br-centroids.cjs.map +1 -1
  44. package/dist/br/data/br-centroids.js +11105 -11045
  45. package/dist/br/data/br-centroids.js.map +1 -1
  46. package/dist/br/data/br-locations.cjs +1 -1
  47. package/dist/br/data/br-locations.cjs.map +1 -1
  48. package/dist/br/data/br-locations.js +5803 -137
  49. package/dist/br/data/br-locations.js.map +1 -1
  50. package/dist/br/data/br-uf-geo.cjs +1 -1
  51. package/dist/br/data/br-uf-geo.cjs.map +1 -1
  52. package/dist/br/data/br-uf-geo.js +5721 -6777
  53. package/dist/br/data/br-uf-geo.js.map +1 -1
  54. package/dist/br/data/mun/AC.cjs +1 -1
  55. package/dist/br/data/mun/AC.cjs.map +1 -1
  56. package/dist/br/data/mun/AC.js +681 -689
  57. package/dist/br/data/mun/AC.js.map +1 -1
  58. package/dist/br/data/mun/AL.cjs +1 -1
  59. package/dist/br/data/mun/AL.cjs.map +1 -1
  60. package/dist/br/data/mun/AL.js +949 -844
  61. package/dist/br/data/mun/AL.js.map +1 -1
  62. package/dist/br/data/mun/AM.cjs +1 -1
  63. package/dist/br/data/mun/AM.cjs.map +1 -1
  64. package/dist/br/data/mun/AM.js +4337 -4218
  65. package/dist/br/data/mun/AM.js.map +1 -1
  66. package/dist/br/data/mun/AP.cjs +1 -1
  67. package/dist/br/data/mun/AP.cjs.map +1 -1
  68. package/dist/br/data/mun/AP.js +786 -780
  69. package/dist/br/data/mun/AP.js.map +1 -1
  70. package/dist/br/data/mun/BA.cjs +1 -1
  71. package/dist/br/data/mun/BA.cjs.map +1 -1
  72. package/dist/br/data/mun/BA.js +6329 -5633
  73. package/dist/br/data/mun/BA.js.map +1 -1
  74. package/dist/br/data/mun/CE.cjs +1 -1
  75. package/dist/br/data/mun/CE.cjs.map +1 -1
  76. package/dist/br/data/mun/CE.js +2544 -2487
  77. package/dist/br/data/mun/CE.js.map +1 -1
  78. package/dist/br/data/mun/DF.cjs +1 -1
  79. package/dist/br/data/mun/DF.cjs.map +1 -1
  80. package/dist/br/data/mun/DF.js +17 -16
  81. package/dist/br/data/mun/DF.js.map +1 -1
  82. package/dist/br/data/mun/ES.cjs +1 -1
  83. package/dist/br/data/mun/ES.cjs.map +1 -1
  84. package/dist/br/data/mun/ES.js +1101 -1070
  85. package/dist/br/data/mun/ES.js.map +1 -1
  86. package/dist/br/data/mun/GO.cjs +1 -1
  87. package/dist/br/data/mun/GO.cjs.map +1 -1
  88. package/dist/br/data/mun/GO.js +4632 -4636
  89. package/dist/br/data/mun/GO.js.map +1 -1
  90. package/dist/br/data/mun/MA.cjs +1 -1
  91. package/dist/br/data/mun/MA.cjs.map +1 -1
  92. package/dist/br/data/mun/MA.js +3603 -3457
  93. package/dist/br/data/mun/MA.js.map +1 -1
  94. package/dist/br/data/mun/MG.cjs +1 -1
  95. package/dist/br/data/mun/MG.cjs.map +1 -1
  96. package/dist/br/data/mun/MG.js +12716 -11739
  97. package/dist/br/data/mun/MG.js.map +1 -1
  98. package/dist/br/data/mun/MS.cjs +1 -1
  99. package/dist/br/data/mun/MS.cjs.map +1 -1
  100. package/dist/br/data/mun/MS.js +2182 -2335
  101. package/dist/br/data/mun/MS.js.map +1 -1
  102. package/dist/br/data/mun/MT.cjs +1 -1
  103. package/dist/br/data/mun/MT.cjs.map +1 -1
  104. package/dist/br/data/mun/MT.js +4808 -4896
  105. package/dist/br/data/mun/MT.js.map +1 -1
  106. package/dist/br/data/mun/PA.cjs +1 -1
  107. package/dist/br/data/mun/PA.cjs.map +1 -1
  108. package/dist/br/data/mun/PA.js +4981 -4901
  109. package/dist/br/data/mun/PA.js.map +1 -1
  110. package/dist/br/data/mun/PB.cjs +1 -1
  111. package/dist/br/data/mun/PB.cjs.map +1 -1
  112. package/dist/br/data/mun/PB.js +2418 -1956
  113. package/dist/br/data/mun/PB.js.map +1 -1
  114. package/dist/br/data/mun/PE.cjs +1 -1
  115. package/dist/br/data/mun/PE.cjs.map +1 -1
  116. package/dist/br/data/mun/PE.js +2164 -1987
  117. package/dist/br/data/mun/PE.js.map +1 -1
  118. package/dist/br/data/mun/PI.cjs +1 -1
  119. package/dist/br/data/mun/PI.cjs.map +1 -1
  120. package/dist/br/data/mun/PI.js +2982 -2844
  121. package/dist/br/data/mun/PI.js.map +1 -1
  122. package/dist/br/data/mun/PR.cjs +1 -1
  123. package/dist/br/data/mun/PR.cjs.map +1 -1
  124. package/dist/br/data/mun/PR.js +5425 -5240
  125. package/dist/br/data/mun/PR.js.map +1 -1
  126. package/dist/br/data/mun/RJ.cjs +1 -1
  127. package/dist/br/data/mun/RJ.cjs.map +1 -1
  128. package/dist/br/data/mun/RJ.js +1395 -1178
  129. package/dist/br/data/mun/RJ.js.map +1 -1
  130. package/dist/br/data/mun/RN.cjs +1 -1
  131. package/dist/br/data/mun/RN.cjs.map +1 -1
  132. package/dist/br/data/mun/RN.js +1608 -1382
  133. package/dist/br/data/mun/RN.js.map +1 -1
  134. package/dist/br/data/mun/RO.cjs +1 -1
  135. package/dist/br/data/mun/RO.cjs.map +1 -1
  136. package/dist/br/data/mun/RO.js +1392 -1470
  137. package/dist/br/data/mun/RO.js.map +1 -1
  138. package/dist/br/data/mun/RR.cjs +1 -1
  139. package/dist/br/data/mun/RR.cjs.map +1 -1
  140. package/dist/br/data/mun/RR.js +795 -778
  141. package/dist/br/data/mun/RR.js.map +1 -1
  142. package/dist/br/data/mun/RS.cjs +1 -1
  143. package/dist/br/data/mun/RS.cjs.map +1 -1
  144. package/dist/br/data/mun/RS.js +7579 -6424
  145. package/dist/br/data/mun/RS.js.map +1 -1
  146. package/dist/br/data/mun/SC.cjs +1 -1
  147. package/dist/br/data/mun/SC.cjs.map +1 -1
  148. package/dist/br/data/mun/SC.js +3979 -3357
  149. package/dist/br/data/mun/SC.js.map +1 -1
  150. package/dist/br/data/mun/SE.cjs +1 -1
  151. package/dist/br/data/mun/SE.cjs.map +1 -1
  152. package/dist/br/data/mun/SE.js +812 -625
  153. package/dist/br/data/mun/SE.js.map +1 -1
  154. package/dist/br/data/mun/SP.cjs +1 -1
  155. package/dist/br/data/mun/SP.cjs.map +1 -1
  156. package/dist/br/data/mun/SP.js +7866 -6803
  157. package/dist/br/data/mun/SP.js.map +1 -1
  158. package/dist/br/data/mun/TO.cjs +1 -1
  159. package/dist/br/data/mun/TO.cjs.map +1 -1
  160. package/dist/br/data/mun/TO.js +2763 -2817
  161. package/dist/br/data/mun/TO.js.map +1 -1
  162. package/dist/br/locations.cjs +1 -1
  163. package/dist/br/locations.cjs.map +1 -1
  164. package/dist/br/locations.js +97 -24
  165. package/dist/br/locations.js.map +1 -1
  166. package/dist/br/scales.cjs +1 -1
  167. package/dist/br/scales.cjs.map +1 -1
  168. package/dist/br/scales.js +8 -4
  169. package/dist/br/scales.js.map +1 -1
  170. package/dist/br/whatsapp.cjs +2 -0
  171. package/dist/br/whatsapp.cjs.map +1 -0
  172. package/dist/br/whatsapp.js +21 -0
  173. package/dist/br/whatsapp.js.map +1 -0
  174. package/dist/br.cjs +1 -1
  175. package/dist/br.cjs.map +1 -1
  176. package/dist/br.d.ts +188 -10
  177. package/dist/br.js +18 -17
  178. package/dist/br.js.map +1 -1
  179. package/dist/components/BottomSheet/BottomSheet.cjs +1 -1
  180. package/dist/components/BottomSheet/BottomSheet.cjs.map +1 -1
  181. package/dist/components/BottomSheet/BottomSheet.js +30 -27
  182. package/dist/components/BottomSheet/BottomSheet.js.map +1 -1
  183. package/dist/components/Command/Command.cjs +1 -1
  184. package/dist/components/Command/Command.cjs.map +1 -1
  185. package/dist/components/Command/Command.js +78 -75
  186. package/dist/components/Command/Command.js.map +1 -1
  187. package/dist/components/Drawer/Drawer.cjs +1 -1
  188. package/dist/components/Drawer/Drawer.cjs.map +1 -1
  189. package/dist/components/Drawer/Drawer.js +42 -39
  190. package/dist/components/Drawer/Drawer.js.map +1 -1
  191. package/dist/components/DropdownMenu/DropdownMenu.cjs +1 -1
  192. package/dist/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
  193. package/dist/components/DropdownMenu/DropdownMenu.js +111 -67
  194. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  195. package/dist/components/Modal/Modal.cjs +1 -1
  196. package/dist/components/Modal/Modal.cjs.map +1 -1
  197. package/dist/components/Modal/Modal.js +45 -42
  198. package/dist/components/Modal/Modal.js.map +1 -1
  199. package/dist/components/Money/Money.cjs +1 -1
  200. package/dist/components/Money/Money.cjs.map +1 -1
  201. package/dist/components/Money/Money.js +10 -9
  202. package/dist/components/Money/Money.js.map +1 -1
  203. package/dist/components/Pagination/Pagination.cjs +1 -1
  204. package/dist/components/Pagination/Pagination.cjs.map +1 -1
  205. package/dist/components/Pagination/Pagination.js +38 -31
  206. package/dist/components/Pagination/Pagination.js.map +1 -1
  207. package/dist/components/Pagination/Pagination.module.cjs +1 -1
  208. package/dist/components/Pagination/Pagination.module.cjs.map +1 -1
  209. package/dist/components/Pagination/Pagination.module.js +4 -4
  210. package/dist/components/Pagination/Pagination.module.js.map +1 -1
  211. package/dist/components/Portal/Portal.cjs +1 -1
  212. package/dist/components/Portal/Portal.cjs.map +1 -1
  213. package/dist/components/Portal/Portal.js +9 -6
  214. package/dist/components/Portal/Portal.js.map +1 -1
  215. package/dist/components/Portal/portal-host.cjs +2 -0
  216. package/dist/components/Portal/portal-host.cjs.map +1 -0
  217. package/dist/components/Portal/portal-host.js +11 -0
  218. package/dist/components/Portal/portal-host.js.map +1 -0
  219. package/dist/components/RangeSlider/RangeSlider.cjs +1 -1
  220. package/dist/components/RangeSlider/RangeSlider.cjs.map +1 -1
  221. package/dist/components/RangeSlider/RangeSlider.js +23 -23
  222. package/dist/components/RangeSlider/RangeSlider.js.map +1 -1
  223. package/dist/components/Scheduler/Scheduler.cjs +1 -1
  224. package/dist/components/Scheduler/Scheduler.cjs.map +1 -1
  225. package/dist/components/Scheduler/Scheduler.js +83 -82
  226. package/dist/components/Scheduler/Scheduler.js.map +1 -1
  227. package/dist/components/Slider/Slider.cjs +1 -1
  228. package/dist/components/Slider/Slider.cjs.map +1 -1
  229. package/dist/components/Slider/Slider.js +9 -9
  230. package/dist/components/Slider/Slider.js.map +1 -1
  231. package/dist/components/Toast/ToastProvider.cjs +1 -1
  232. package/dist/components/Toast/ToastProvider.cjs.map +1 -1
  233. package/dist/components/Toast/ToastProvider.js +55 -53
  234. package/dist/components/Toast/ToastProvider.js.map +1 -1
  235. package/dist/components/VirtualList/VirtualList.cjs +1 -1
  236. package/dist/components/VirtualList/VirtualList.cjs.map +1 -1
  237. package/dist/components/VirtualList/VirtualList.js +41 -31
  238. package/dist/components/VirtualList/VirtualList.js.map +1 -1
  239. package/dist/components/VirtualTable/VirtualTable.cjs +1 -1
  240. package/dist/components/VirtualTable/VirtualTable.cjs.map +1 -1
  241. package/dist/components/VirtualTable/VirtualTable.js +78 -68
  242. package/dist/components/VirtualTable/VirtualTable.js.map +1 -1
  243. package/dist/forms/FormField.cjs +1 -1
  244. package/dist/forms/FormField.cjs.map +1 -1
  245. package/dist/forms/FormField.js +23 -21
  246. package/dist/forms/FormField.js.map +1 -1
  247. package/dist/forms/masked-inputs.cjs +1 -1
  248. package/dist/forms/masked-inputs.cjs.map +1 -1
  249. package/dist/forms/masked-inputs.js +20 -19
  250. package/dist/forms/masked-inputs.js.map +1 -1
  251. package/dist/hooks/use-fullscreen-element.cjs +2 -0
  252. package/dist/hooks/use-fullscreen-element.cjs.map +1 -0
  253. package/dist/hooks/use-fullscreen-element.js +20 -0
  254. package/dist/hooks/use-fullscreen-element.js.map +1 -0
  255. package/dist/hooks/use-fullscreen.cjs +2 -0
  256. package/dist/hooks/use-fullscreen.cjs.map +1 -0
  257. package/dist/hooks/use-fullscreen.js +66 -0
  258. package/dist/hooks/use-fullscreen.js.map +1 -0
  259. package/dist/hooks/use-push-to-talk.cjs +2 -0
  260. package/dist/hooks/use-push-to-talk.cjs.map +1 -0
  261. package/dist/hooks/use-push-to-talk.js +61 -0
  262. package/dist/hooks/use-push-to-talk.js.map +1 -0
  263. package/dist/http/describe-api-error.cjs.map +1 -1
  264. package/dist/http/describe-api-error.js.map +1 -1
  265. package/dist/http/errors.cjs +1 -1
  266. package/dist/http/errors.cjs.map +1 -1
  267. package/dist/http/errors.js +34 -16
  268. package/dist/http/errors.js.map +1 -1
  269. package/dist/http/parse-response.cjs +2 -2
  270. package/dist/http/parse-response.cjs.map +1 -1
  271. package/dist/http/parse-response.js +9 -8
  272. package/dist/http/parse-response.js.map +1 -1
  273. package/dist/i18n/create-i18n.cjs +1 -1
  274. package/dist/i18n/create-i18n.cjs.map +1 -1
  275. package/dist/i18n/create-i18n.js +28 -27
  276. package/dist/i18n/create-i18n.js.map +1 -1
  277. package/dist/query/QueryProvider.cjs +1 -1
  278. package/dist/query/QueryProvider.cjs.map +1 -1
  279. package/dist/query/QueryProvider.js +13 -11
  280. package/dist/query/QueryProvider.js.map +1 -1
  281. package/dist/query/foreign-client-warning.cjs +2 -0
  282. package/dist/query/foreign-client-warning.cjs.map +1 -0
  283. package/dist/query/foreign-client-warning.js +12 -0
  284. package/dist/query/foreign-client-warning.js.map +1 -0
  285. package/dist/router/AppRouter.cjs +1 -1
  286. package/dist/router/AppRouter.cjs.map +1 -1
  287. package/dist/router/AppRouter.js +28 -21
  288. package/dist/router/AppRouter.js.map +1 -1
  289. package/dist/router/RouteGuard.cjs +1 -1
  290. package/dist/router/RouteGuard.cjs.map +1 -1
  291. package/dist/router/RouteGuard.js +4 -4
  292. package/dist/router/RouteGuard.js.map +1 -1
  293. package/dist/sse/create-event-stream.cjs +1 -1
  294. package/dist/sse/create-event-stream.cjs.map +1 -1
  295. package/dist/sse/create-event-stream.js +34 -38
  296. package/dist/sse/create-event-stream.js.map +1 -1
  297. package/dist/styles/AIChat.css +79 -0
  298. package/dist/styles/Accordion.css +12 -0
  299. package/dist/styles/Alert.css +32 -0
  300. package/dist/styles/AppBar.css +17 -0
  301. package/dist/styles/AppShell.css +8 -0
  302. package/dist/styles/AspectRatio.css +3 -0
  303. package/dist/styles/AudioPlayer.css +13 -0
  304. package/dist/styles/AudioRecorder.css +20 -0
  305. package/dist/styles/Avatar.css +13 -0
  306. package/dist/styles/AvatarGroup.css +18 -0
  307. package/dist/styles/Badge.css +25 -0
  308. package/dist/styles/Banner.css +13 -0
  309. package/dist/styles/BarList.css +10 -0
  310. package/dist/styles/BarcodeScanner.css +21 -0
  311. package/dist/styles/BottomNavigation.css +10 -0
  312. package/dist/styles/BottomSheet.css +11 -0
  313. package/dist/styles/BrazilMap.css +20 -0
  314. package/dist/styles/Breadcrumbs.css +8 -0
  315. package/dist/styles/Button.css +42 -0
  316. package/dist/styles/Calendar.css +16 -0
  317. package/dist/styles/Card.css +15 -0
  318. package/dist/styles/Carousel.css +17 -0
  319. package/dist/styles/Center.css +5 -0
  320. package/dist/styles/Chat.css +35 -0
  321. package/dist/styles/Checkbox.css +11 -0
  322. package/dist/styles/ChipInput.css +13 -0
  323. package/dist/styles/CodeBlock.css +25 -0
  324. package/dist/styles/Collapsible.css +6 -0
  325. package/dist/styles/Combobox.css +18 -0
  326. package/dist/styles/Command.css +20 -0
  327. package/dist/styles/ContextMenu.css +11 -0
  328. package/dist/styles/CopyButton.css +4 -0
  329. package/dist/styles/DataList.css +3 -0
  330. package/dist/styles/DataTable.css +19 -0
  331. package/dist/styles/DateRangePicker.css +22 -0
  332. package/dist/styles/DescriptionList.css +4 -0
  333. package/dist/styles/Divider.css +11 -0
  334. package/dist/styles/Drawer.css +22 -0
  335. package/dist/styles/DropdownMenu.css +16 -0
  336. package/dist/styles/Dropzone.css +7 -0
  337. package/dist/styles/EmptyState.css +6 -0
  338. package/dist/styles/ErrorState.css +6 -0
  339. package/dist/styles/ErrorText.css +2 -0
  340. package/dist/styles/FileUpload.css +17 -0
  341. package/dist/styles/FilterBar.css +19 -0
  342. package/dist/styles/FloatingActionButton.css +23 -0
  343. package/dist/styles/Form.css +21 -0
  344. package/dist/styles/HoverCard.css +11 -0
  345. package/dist/styles/IconPicker.css +6 -0
  346. package/dist/styles/Image.css +2 -0
  347. package/dist/styles/ImageCropper.css +16 -0
  348. package/dist/styles/Input.css +24 -0
  349. package/dist/styles/InstallBanner.css +8 -0
  350. package/dist/styles/Kanban.css +15 -0
  351. package/dist/styles/Kbd.css +5 -0
  352. package/dist/styles/Label.css +3 -0
  353. package/dist/styles/Layout.css +22 -0
  354. package/dist/styles/Lightbox.css +21 -0
  355. package/dist/styles/ListTile.css +14 -0
  356. package/dist/styles/MapLegend.css +9 -0
  357. package/dist/styles/Markdown.css +24 -0
  358. package/dist/styles/Masonry.css +4 -0
  359. package/dist/styles/Menubar.css +17 -0
  360. package/dist/styles/Modal.css +21 -0
  361. package/dist/styles/ModalsManager.css +2 -0
  362. package/dist/styles/MultiSelect.css +21 -0
  363. package/dist/styles/MunicipalitySearch.css +6 -0
  364. package/dist/styles/NProgress.css +2 -0
  365. package/dist/styles/Navbar.css +10 -0
  366. package/dist/styles/NavigationMenu.css +12 -0
  367. package/dist/styles/NavigationRail.css +14 -0
  368. package/dist/styles/NotificationCenter.css +21 -0
  369. package/dist/styles/OfflineIndicator.css +7 -0
  370. package/dist/styles/Page.css +13 -0
  371. package/dist/styles/Pagination.css +12 -0
  372. package/dist/styles/PasswordInput.css +21 -0
  373. package/dist/styles/PinInput.css +13 -0
  374. package/dist/styles/PixQRCode.css +9 -0
  375. package/dist/styles/Popover.css +10 -0
  376. package/dist/styles/Progress.css +11 -0
  377. package/dist/styles/QRCode.css +3 -0
  378. package/dist/styles/Radio.css +14 -0
  379. package/dist/styles/RangeSlider.css +15 -0
  380. package/dist/styles/RatingStars.css +11 -0
  381. package/dist/styles/RefreshIndicator.css +5 -0
  382. package/dist/styles/Resizable.css +13 -0
  383. package/dist/styles/RichTextEditor.css +19 -0
  384. package/dist/styles/SafeArea.css +7 -0
  385. package/dist/styles/Scheduler.css +24 -0
  386. package/dist/styles/ScrollArea.css +7 -0
  387. package/dist/styles/SearchBar.css +11 -0
  388. package/dist/styles/SegmentedControl.css +12 -0
  389. package/dist/styles/Select.css +14 -0
  390. package/dist/styles/Sidebar.css +19 -0
  391. package/dist/styles/SignaturePad.css +6 -0
  392. package/dist/styles/Skeleton.css +7 -0
  393. package/dist/styles/Slider.css +15 -0
  394. package/dist/styles/Spacer.css +4 -0
  395. package/dist/styles/Sparkline.css +4 -0
  396. package/dist/styles/Spinner.css +12 -0
  397. package/dist/styles/Stat.css +12 -0
  398. package/dist/styles/Stepper.css +17 -0
  399. package/dist/styles/StepperInput.css +15 -0
  400. package/dist/styles/Switch.css +11 -0
  401. package/dist/styles/SyncStatusBadge.css +13 -0
  402. package/dist/styles/Table.css +16 -0
  403. package/dist/styles/Tabs.css +14 -0
  404. package/dist/styles/Tag.css +14 -0
  405. package/dist/styles/Textarea.css +13 -0
  406. package/dist/styles/TimePicker.css +14 -0
  407. package/dist/styles/Timeline.css +17 -0
  408. package/dist/styles/Toast.css +21 -0
  409. package/dist/styles/Toggle.css +12 -0
  410. package/dist/styles/ToggleGroup.css +9 -0
  411. package/dist/styles/Tooltip.css +10 -0
  412. package/dist/styles/Tour.css +20 -0
  413. package/dist/styles/TrajectoryMap.css +16 -0
  414. package/dist/styles/Transfer.css +21 -0
  415. package/dist/styles/TreeView.css +17 -0
  416. package/dist/styles/TruncateText.css +2 -0
  417. package/dist/styles/UpdatePrompt.css +10 -0
  418. package/dist/styles/VirtualList.css +5 -0
  419. package/dist/styles/VirtualTable.css +17 -0
  420. package/dist/styles/VisuallyHidden.css +2 -0
  421. package/dist/styles/Wizard.css +4 -0
  422. package/dist/styles/actions.css +104 -0
  423. package/dist/styles/advanced.css +66 -0
  424. package/dist/styles/br.css +41 -0
  425. package/dist/styles/chat.css +113 -0
  426. package/dist/styles/core.css +40 -0
  427. package/dist/styles/data.css +222 -0
  428. package/dist/styles/editor.css +19 -0
  429. package/dist/styles/feedback.css +201 -0
  430. package/dist/styles/forms.css +321 -0
  431. package/dist/styles/geo.css +16 -0
  432. package/dist/styles/icons.css +6 -0
  433. package/dist/styles/identity.css +48 -0
  434. package/dist/styles/layout.css +122 -0
  435. package/dist/styles/media.css +52 -0
  436. package/dist/styles/navigation.css +167 -0
  437. package/dist/styles/overlay.css +73 -0
  438. package/dist/styles/utility.css +39 -0
  439. package/dist/styles.css +1 -1
  440. package/dist/sw/register-service-worker.cjs +1 -1
  441. package/dist/sw/register-service-worker.cjs.map +1 -1
  442. package/dist/sw/register-service-worker.js +21 -17
  443. package/dist/sw/register-service-worker.js.map +1 -1
  444. package/dist/sw.d.ts +17 -1
  445. package/dist/tempest-react-sdk.cjs +1 -1
  446. package/dist/tempest-react-sdk.d.ts +1382 -76
  447. package/dist/tempest-react-sdk.js +293 -281
  448. package/dist/utils/dev-mode.cjs.map +1 -1
  449. package/dist/utils/dev-mode.js.map +1 -1
  450. package/dist/utils/duplicate-instance.cjs +2 -0
  451. package/dist/utils/duplicate-instance.cjs.map +1 -0
  452. package/dist/utils/duplicate-instance.js +6 -0
  453. package/dist/utils/duplicate-instance.js.map +1 -0
  454. package/dist/utils/format.cjs +1 -1
  455. package/dist/utils/format.cjs.map +1 -1
  456. package/dist/utils/format.js +22 -14
  457. package/dist/utils/format.js.map +1 -1
  458. package/dist/utils/intl-cache.cjs +2 -0
  459. package/dist/utils/intl-cache.cjs.map +1 -0
  460. package/dist/utils/intl-cache.js +23 -0
  461. package/dist/utils/intl-cache.js.map +1 -0
  462. package/dist/utils/json-frame.cjs +2 -0
  463. package/dist/utils/json-frame.cjs.map +1 -0
  464. package/dist/utils/json-frame.js +30 -0
  465. package/dist/utils/json-frame.js.map +1 -0
  466. package/dist/utils/numbers.cjs +1 -1
  467. package/dist/utils/numbers.cjs.map +1 -1
  468. package/dist/utils/numbers.js +8 -7
  469. package/dist/utils/numbers.js.map +1 -1
  470. package/dist/vision/results.cjs +1 -1
  471. package/dist/vision/results.cjs.map +1 -1
  472. package/dist/vision/results.js +26 -17
  473. package/dist/vision/results.js.map +1 -1
  474. package/dist/vision.d.ts +29 -0
  475. package/dist/vite/tempest-icons.cjs +1 -1
  476. package/dist/vite/tempest-icons.cjs.map +1 -1
  477. package/dist/vite/tempest-icons.js +1 -1
  478. package/dist/vite/tempest-icons.js.map +1 -1
  479. package/dist/webrtc/link-stats.cjs +2 -0
  480. package/dist/webrtc/link-stats.cjs.map +1 -0
  481. package/dist/webrtc/link-stats.js +74 -0
  482. package/dist/webrtc/link-stats.js.map +1 -0
  483. package/dist/webrtc/opus-sdp.cjs +3 -0
  484. package/dist/webrtc/opus-sdp.cjs.map +1 -0
  485. package/dist/webrtc/opus-sdp.js +94 -0
  486. package/dist/webrtc/opus-sdp.js.map +1 -0
  487. package/dist/webrtc/sender-bitrate.cjs +2 -0
  488. package/dist/webrtc/sender-bitrate.cjs.map +1 -0
  489. package/dist/webrtc/sender-bitrate.js +15 -0
  490. package/dist/webrtc/sender-bitrate.js.map +1 -0
  491. package/dist/webrtc/use-link-stats.cjs +2 -0
  492. package/dist/webrtc/use-link-stats.cjs.map +1 -0
  493. package/dist/webrtc/use-link-stats.js +22 -0
  494. package/dist/webrtc/use-link-stats.js.map +1 -0
  495. package/dist/ws/create-web-socket.cjs +1 -1
  496. package/dist/ws/create-web-socket.cjs.map +1 -1
  497. package/dist/ws/create-web-socket.js +130 -58
  498. package/dist/ws/create-web-socket.js.map +1 -1
  499. package/dist/ws/resilience.cjs +2 -0
  500. package/dist/ws/resilience.cjs.map +1 -0
  501. package/dist/ws/resilience.js +21 -0
  502. package/dist/ws/resilience.js.map +1 -0
  503. package/dist/ws/use-web-socket.cjs +1 -1
  504. package/dist/ws/use-web-socket.cjs.map +1 -1
  505. package/dist/ws/use-web-socket.js +47 -39
  506. package/dist/ws/use-web-socket.js.map +1 -1
  507. package/package.json +5 -2
@@ -0,0 +1,94 @@
1
+ //#region src/webrtc/opus-sdp.ts
2
+ var e = /* @__PURE__ */ new Set([
3
+ "maxAverageBitrate",
4
+ "maxPlaybackRate",
5
+ "stereo",
6
+ "fec",
7
+ "dtx",
8
+ "cbr",
9
+ "extra"
10
+ ]);
11
+ function t(t) {
12
+ let n = Object.keys(t);
13
+ return n.length === 0 || n.some((t) => e.has(t));
14
+ }
15
+ function n(e) {
16
+ let t = {};
17
+ return e.maxAverageBitrate !== void 0 && (t.maxaveragebitrate = String(e.maxAverageBitrate)), e.maxPlaybackRate !== void 0 && (t.maxplaybackrate = String(e.maxPlaybackRate)), e.stereo !== void 0 && (t.stereo = e.stereo ? "1" : "0", t["sprop-stereo"] = e.stereo ? "1" : "0"), e.fec !== void 0 && (t.useinbandfec = e.fec ? "1" : "0"), e.dtx !== void 0 && (t.usedtx = e.dtx ? "1" : "0"), e.cbr !== void 0 && (t.cbr = e.cbr ? "1" : "0"), {
18
+ ...t,
19
+ ...e.extra
20
+ };
21
+ }
22
+ function r(e, t) {
23
+ let n = /* @__PURE__ */ new Map();
24
+ for (let t of e.split(";")) {
25
+ let e = t.trim();
26
+ if (!e) continue;
27
+ let r = e.indexOf("=");
28
+ r < 0 ? n.set(e, "") : n.set(e.slice(0, r), e.slice(r + 1));
29
+ }
30
+ for (let [e, r] of Object.entries(t)) n.set(e, r);
31
+ return [...n.entries()].map(([e, t]) => t === "" ? e : `${e}=${t}`).join(";");
32
+ }
33
+ function i(e) {
34
+ let t = [];
35
+ for (let n of e) {
36
+ let e = /^a=rtpmap:(\d+)\s+opus\/48000/i.exec(n);
37
+ e && t.push(e[1]);
38
+ }
39
+ return t;
40
+ }
41
+ function a(e) {
42
+ for (let t of e) {
43
+ let e = /^a=mid:(.+)$/.exec(t);
44
+ if (e) return e[1].trim();
45
+ }
46
+ return null;
47
+ }
48
+ function o(e, t) {
49
+ let a = n(t), o = i(e);
50
+ if (o.length === 0 || Object.keys(a).length === 0) return [...e];
51
+ let s = [];
52
+ for (let t of e) {
53
+ let n = /^a=fmtp:(\d+)\s+(.*)$/.exec(t);
54
+ if (n && o.includes(n[1])) {
55
+ s.push(`a=fmtp:${n[1]} ${r(n[2], a)}`);
56
+ continue;
57
+ }
58
+ s.push(t);
59
+ let i = /^a=rtpmap:(\d+)\s+opus\/48000/i.exec(t);
60
+ i && !e.some((e) => e.startsWith(`a=fmtp:${i[1]} `)) && s.push(`a=fmtp:${i[1]} ${r("", a)}`);
61
+ }
62
+ return s;
63
+ }
64
+ function s(e) {
65
+ let t = [], n = [];
66
+ for (let r of e.split(/\r\n|\n/)) r.startsWith("m=") && n.length > 0 && (t.push(n), n = []), n.push(r);
67
+ return n.length > 0 && t.push(n), t;
68
+ }
69
+ function c(e, n) {
70
+ let r = t(n) ? n : null, i = r ? null : n, c = 0;
71
+ return s(e).map((e) => {
72
+ if (!e[0]?.startsWith("m=audio")) return e;
73
+ let t = c;
74
+ if (c += 1, r) return o(e, r);
75
+ let n = a(e), s = i?.[t] ?? (n === null ? void 0 : i?.[n]);
76
+ return s ? o(e, s) : e;
77
+ }).flat().join("\r\n");
78
+ }
79
+ async function l(e, t, n) {
80
+ if (t.sdp) {
81
+ let r = c(t.sdp, n);
82
+ if (r !== t.sdp) try {
83
+ return await e.setLocalDescription({
84
+ ...t,
85
+ sdp: r
86
+ }), "tuned";
87
+ } catch {}
88
+ }
89
+ return await e.setLocalDescription(t), "original";
90
+ }
91
+ //#endregion
92
+ export { l as setTunedLocalDescription, c as tuneOpus };
93
+
94
+ //# sourceMappingURL=opus-sdp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opus-sdp.js","names":[],"sources":["../../src/webrtc/opus-sdp.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines — parsing a session description, merging one\n * `fmtp` line and inserting a missing one are the same pass over the same\n * grammar; split apart, each half is a parser of half an SDP.\n */\n\n/** What an Opus `fmtp` line can be asked to carry. Every field is optional. */\nexport interface OpusProfile {\n /**\n * Ceiling the encoder is asked to respect, in bits per second.\n *\n * This describes what we want **to receive**. To cap what we *send*, use\n * `setSenderBitrate` — in a mesh that is the one that matters, because the\n * uplink carries one copy per participant.\n */\n maxAverageBitrate?: number;\n /** Highest sample rate worth decoding, in Hz. `48000` for full band. */\n maxPlaybackRate?: number;\n /**\n * Two channels instead of one.\n *\n * Sets `stereo` **and** `sprop-stereo`, which point in opposite directions:\n * `stereo=1` asks the *remote* to send two channels, `sprop-stereo=1`\n * announces that *we* will. Setting only one leaves the link asymmetric,\n * which is the recurring reason \"I asked for stereo and got mono\".\n */\n stereo?: boolean;\n /**\n * In-band forward error correction (`useinbandfec`).\n *\n * Rebuilds a lost packet from the next one, which keeps speech intelligible\n * on a lossy link — and smears music, since it spends bitrate on redundancy\n * instead of detail.\n */\n fec?: boolean;\n /**\n * Discontinuous transmission (`usedtx`): stop sending during silence.\n *\n * Saves uplink in a mesh, at the cost of clipping the first instant after a\n * pause. Wrong for music and for a shared screen, where the quiet passages\n * are content.\n */\n dtx?: boolean;\n /** Constant bitrate (`cbr`). Off by default, as Opus intends. */\n cbr?: boolean;\n /**\n * Any other `fmtp` key, merged verbatim.\n *\n * The escape hatch for a parameter this type does not model. Values are\n * written as given; a key with an empty string is emitted as a bare flag.\n */\n extra?: Record<string, string>;\n}\n\n/**\n * Profiles to apply, keyed by audio m-line index (`0`, `1`, …) or by `mid`.\n *\n * Position and `mid` can be mixed in one object. A key that matches nothing is\n * ignored rather than an error — an SDP is negotiated, and a slot that was not\n * offered this time is normal.\n */\nexport type OpusProfileMap = Record<string | number, OpusProfile>;\n\n/** Keys that identify a bare {@link OpusProfile} rather than a map of them. */\nconst PROFILE_KEYS: ReadonlySet<string> = new Set([\n \"maxAverageBitrate\",\n \"maxPlaybackRate\",\n \"stereo\",\n \"fec\",\n \"dtx\",\n \"cbr\",\n \"extra\",\n]);\n\n/**\n * Whether the caller passed one profile for every audio m-line, or a map.\n *\n * An empty object is read as a profile, which makes `tuneOpus(sdp, {})` a no-op\n * instead of a silent surprise.\n */\nfunction isSingleProfile(value: OpusProfile | OpusProfileMap): value is OpusProfile {\n const keys = Object.keys(value);\n return keys.length === 0 || keys.some((key) => PROFILE_KEYS.has(key));\n}\n\n/** The `fmtp` parameters a profile asks for, in the order Opus documents them. */\nfunction fmtpParams(profile: OpusProfile): Record<string, string> {\n const params: Record<string, string> = {};\n if (profile.maxAverageBitrate !== undefined) {\n params.maxaveragebitrate = String(profile.maxAverageBitrate);\n }\n if (profile.maxPlaybackRate !== undefined) {\n params.maxplaybackrate = String(profile.maxPlaybackRate);\n }\n if (profile.stereo !== undefined) {\n params.stereo = profile.stereo ? \"1\" : \"0\";\n params[\"sprop-stereo\"] = profile.stereo ? \"1\" : \"0\";\n }\n if (profile.fec !== undefined) params.useinbandfec = profile.fec ? \"1\" : \"0\";\n if (profile.dtx !== undefined) params.usedtx = profile.dtx ? \"1\" : \"0\";\n if (profile.cbr !== undefined) params.cbr = profile.cbr ? \"1\" : \"0\";\n return { ...params, ...profile.extra };\n}\n\n/**\n * Merge parameters into an existing `fmtp` payload, key by key.\n *\n * Replacing the whole line is the obvious move and it is wrong: the browser\n * emits `minptime=10;useinbandfec=1` on its own, and overwriting drops the\n * `minptime` — a packetization decision nobody meant to change.\n */\nfunction mergeFmtp(existing: string, params: Record<string, string>): string {\n const merged = new Map<string, string>();\n for (const entry of existing.split(\";\")) {\n const trimmed = entry.trim();\n if (!trimmed) continue;\n const eq = trimmed.indexOf(\"=\");\n if (eq < 0) merged.set(trimmed, \"\");\n else merged.set(trimmed.slice(0, eq), trimmed.slice(eq + 1));\n }\n for (const [key, value] of Object.entries(params)) merged.set(key, value);\n return [...merged.entries()]\n .map(([key, value]) => (value === \"\" ? key : `${key}=${value}`))\n .join(\";\");\n}\n\n/** Payload types this media block maps to Opus. There can be more than one. */\nfunction opusPayloads(block: readonly string[]): string[] {\n const payloads: string[] = [];\n for (const line of block) {\n const match = /^a=rtpmap:(\\d+)\\s+opus\\/48000/i.exec(line);\n if (match) payloads.push(match[1]);\n }\n return payloads;\n}\n\n/** The block's `mid`, when it declares one. */\nfunction blockMid(block: readonly string[]): string | null {\n for (const line of block) {\n const match = /^a=mid:(.+)$/.exec(line);\n if (match) return match[1].trim();\n }\n return null;\n}\n\n/**\n * Rewrite one media block's Opus `fmtp` lines to carry a profile.\n *\n * A payload type with no `fmtp` of its own gets one inserted right after its\n * `rtpmap`, because \"the parameter is missing\" and \"the parameter is empty\" are\n * the same request from the caller's side.\n */\nfunction tuneBlock(block: readonly string[], profile: OpusProfile): string[] {\n const params = fmtpParams(profile);\n const payloads = opusPayloads(block);\n if (payloads.length === 0 || Object.keys(params).length === 0) return [...block];\n\n const out: string[] = [];\n for (const line of block) {\n const fmtp = /^a=fmtp:(\\d+)\\s+(.*)$/.exec(line);\n if (fmtp && payloads.includes(fmtp[1])) {\n out.push(`a=fmtp:${fmtp[1]} ${mergeFmtp(fmtp[2], params)}`);\n continue;\n }\n out.push(line);\n\n const rtpmap = /^a=rtpmap:(\\d+)\\s+opus\\/48000/i.exec(line);\n if (rtpmap && !block.some((other) => other.startsWith(`a=fmtp:${rtpmap[1]} `))) {\n out.push(`a=fmtp:${rtpmap[1]} ${mergeFmtp(\"\", params)}`);\n }\n }\n return out;\n}\n\n/** Split a session description into its session part plus one block per m-line. */\nfunction mediaBlocks(sdp: string): string[][] {\n const blocks: string[][] = [];\n let current: string[] = [];\n for (const line of sdp.split(/\\r\\n|\\n/)) {\n if (line.startsWith(\"m=\") && current.length > 0) {\n blocks.push(current);\n current = [];\n }\n current.push(line);\n }\n if (current.length > 0) blocks.push(current);\n return blocks;\n}\n\n/**\n * Apply Opus profiles to the audio m-lines of a session description.\n *\n * Audio over WebRTC is mono and narrow by default, and the only place that is\n * corrected is the SDP — which is why shared-screen audio famously sounds like a\n * telephone: music and video inherit the speech profile (mono, ~32 kbps, FEC on,\n * DTX gating the quiet passages) and no high-level API lets you change it.\n *\n * Deliberately **without** built-in presets: which values to use is the\n * consumer's call — voice in a mesh does not want what system audio wants — and\n * a preset table is the kind of thing that has no business inside a dependency.\n * What lives here is the parsing, merging and insertion, which is where the long\n * tail is. Getting any of it wrong degrades silently: nobody sees an exception,\n * the audio is just worse.\n *\n * @param sdp - The description from `createOffer` or `createAnswer`.\n * @param profiles - One {@link OpusProfile} for every audio m-line, or an\n * {@link OpusProfileMap} keyed by audio m-line index or by `mid`.\n * @returns The rewritten SDP, with CRLF line endings as RFC 4566 requires.\n * Untouched when there is no Opus, when no profile matches, or when a profile\n * asks for nothing.\n *\n * @example\n * const tuned = tuneOpus(offer.sdp, {\n * 0: { maxAverageBitrate: 48_000, stereo: false, fec: true, dtx: true },\n * 1: { maxAverageBitrate: 192_000, stereo: true, fec: false, dtx: false },\n * });\n *\n * @example\n * const tuned = tuneOpus(offer.sdp, { stereo: true, dtx: false });\n */\nexport function tuneOpus(sdp: string, profiles: OpusProfile | OpusProfileMap): string {\n const single = isSingleProfile(profiles) ? profiles : null;\n const map = single ? null : (profiles as OpusProfileMap);\n\n let audioIndex = 0;\n const tuned = mediaBlocks(sdp).map((block) => {\n if (!block[0]?.startsWith(\"m=audio\")) return block;\n\n const index = audioIndex;\n audioIndex += 1;\n if (single) return tuneBlock(block, single);\n\n const mid = blockMid(block);\n const profile = map?.[index] ?? (mid !== null ? map?.[mid] : undefined);\n return profile ? tuneBlock(block, profile) : block;\n });\n\n return tuned.flat().join(\"\\r\\n\");\n}\n\n/** Which description a peer connection actually accepted. */\nexport type TunedDescriptionResult = \"tuned\" | \"original\";\n\n/**\n * Set a local description, falling back to the untouched one if it is refused.\n *\n * Chrome has been tightening what `setLocalDescription` accepts from edited SDP,\n * and there is no way to know in advance. Without a fallback the call dies\n * instead of merely losing the profile — which is the wrong trade by a wide\n * margin: worse audio beats no audio.\n *\n * @param connection - The peer connection.\n * @param description - The description from `createOffer` / `createAnswer`.\n * @param profiles - Passed straight to {@link tuneOpus}.\n * @returns `\"tuned\"` when the rewritten SDP was accepted, `\"original\"` when the\n * fallback was used — worth reporting, because it means the profile silently\n * did not apply.\n * @throws Whatever `setLocalDescription` throws for the original description: at\n * that point the failure is not about the rewrite and the caller has to know.\n *\n * @example\n * const applied = await setTunedLocalDescription(pc, await pc.createOffer(), profiles);\n * if (applied === \"original\") logger.warn(\"opus profile refused by the browser\");\n */\nexport async function setTunedLocalDescription(\n connection: RTCPeerConnection,\n description: RTCSessionDescriptionInit,\n profiles: OpusProfile | OpusProfileMap,\n): Promise<TunedDescriptionResult> {\n if (description.sdp) {\n const sdp = tuneOpus(description.sdp, profiles);\n if (sdp !== description.sdp) {\n try {\n await connection.setLocalDescription({ ...description, sdp });\n return \"tuned\";\n } catch {\n /* the browser refused the edit; the untouched offer still works */\n }\n }\n }\n await connection.setLocalDescription(description);\n return \"original\";\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ async function e(e,t){try{let n=e.getParameters();(!n.encodings||n.encodings.length===0)&&(n.encodings=[{}]);for(let e of n.encodings)t===null?delete e.maxBitrate:e.maxBitrate=t;return await e.setParameters(n),!0}catch{return!1}}exports.setSenderBitrate=e;
2
+ //# sourceMappingURL=sender-bitrate.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sender-bitrate.cjs","names":[],"sources":["../../src/webrtc/sender-bitrate.ts"],"sourcesContent":["/**\n * Cap what one sender transmits, in bits per second.\n *\n * This is the other half of the pair that confuses people: an Opus `fmtp` line\n * describes what we want **to receive**, while this is what limits what we\n * **send**. Both are needed, and in a mesh topology this one matters more,\n * because the uplink carries one copy of the stream per participant.\n *\n * The read-modify-write is not ceremony. `setParameters` only accepts the very\n * object `getParameters` handed back — a freshly built one is rejected — and a\n * sender that has not negotiated yet reports **no** encodings at all, so writing\n * to `encodings[0]` without checking throws on exactly the call that sets the\n * cap before the first offer.\n *\n * @param sender - The `RTCRtpSender` to cap, from `pc.getSenders()` or\n * `transceiver.sender`.\n * @param maxBitrate - Ceiling in bits per second, or `null` to lift the cap.\n * @returns `false` when the browser refused the change, which happens when the\n * sender has no track or the transceiver is gone. Playback continues\n * uncapped — a call that keeps running beats one that throws over a bitrate.\n *\n * @example\n * const sender = pc.getSenders().find((s) => s.track?.kind === \"audio\");\n * if (sender) await setSenderBitrate(sender, 48_000);\n */\nexport async function setSenderBitrate(\n sender: RTCRtpSender,\n maxBitrate: number | null,\n): Promise<boolean> {\n try {\n const parameters = sender.getParameters();\n if (!parameters.encodings || parameters.encodings.length === 0) {\n parameters.encodings = [{}];\n }\n for (const encoding of parameters.encodings) {\n if (maxBitrate === null) delete encoding.maxBitrate;\n else encoding.maxBitrate = maxBitrate;\n }\n await sender.setParameters(parameters);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"AAyBA,eAAsB,EAClB,EACA,EACgB,CAChB,GAAI,CACA,IAAM,EAAa,EAAO,cAAc,GACpC,CAAC,EAAW,WAAa,EAAW,UAAU,SAAW,KACzD,EAAW,UAAY,CAAC,CAAC,CAAC,GAE9B,IAAK,IAAM,KAAY,EAAW,UAC1B,IAAe,KAAM,OAAO,EAAS,WACpC,EAAS,WAAa,EAG/B,OADA,MAAM,EAAO,cAAc,CAAU,EAC9B,EACX,MAAQ,CACJ,MAAO,EACX,CACJ"}
@@ -0,0 +1,15 @@
1
+ //#region src/webrtc/sender-bitrate.ts
2
+ async function e(e, t) {
3
+ try {
4
+ let n = e.getParameters();
5
+ (!n.encodings || n.encodings.length === 0) && (n.encodings = [{}]);
6
+ for (let e of n.encodings) t === null ? delete e.maxBitrate : e.maxBitrate = t;
7
+ return await e.setParameters(n), !0;
8
+ } catch {
9
+ return !1;
10
+ }
11
+ }
12
+ //#endregion
13
+ export { e as setSenderBitrate };
14
+
15
+ //# sourceMappingURL=sender-bitrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sender-bitrate.js","names":[],"sources":["../../src/webrtc/sender-bitrate.ts"],"sourcesContent":["/**\n * Cap what one sender transmits, in bits per second.\n *\n * This is the other half of the pair that confuses people: an Opus `fmtp` line\n * describes what we want **to receive**, while this is what limits what we\n * **send**. Both are needed, and in a mesh topology this one matters more,\n * because the uplink carries one copy of the stream per participant.\n *\n * The read-modify-write is not ceremony. `setParameters` only accepts the very\n * object `getParameters` handed back — a freshly built one is rejected — and a\n * sender that has not negotiated yet reports **no** encodings at all, so writing\n * to `encodings[0]` without checking throws on exactly the call that sets the\n * cap before the first offer.\n *\n * @param sender - The `RTCRtpSender` to cap, from `pc.getSenders()` or\n * `transceiver.sender`.\n * @param maxBitrate - Ceiling in bits per second, or `null` to lift the cap.\n * @returns `false` when the browser refused the change, which happens when the\n * sender has no track or the transceiver is gone. Playback continues\n * uncapped — a call that keeps running beats one that throws over a bitrate.\n *\n * @example\n * const sender = pc.getSenders().find((s) => s.track?.kind === \"audio\");\n * if (sender) await setSenderBitrate(sender, 48_000);\n */\nexport async function setSenderBitrate(\n sender: RTCRtpSender,\n maxBitrate: number | null,\n): Promise<boolean> {\n try {\n const parameters = sender.getParameters();\n if (!parameters.encodings || parameters.encodings.length === 0) {\n parameters.encodings = [{}];\n }\n for (const encoding of parameters.encodings) {\n if (maxBitrate === null) delete encoding.maxBitrate;\n else encoding.maxBitrate = maxBitrate;\n }\n await sender.setParameters(parameters);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":";AAyBA,eAAsB,EAClB,GACA,GACgB;CAChB,IAAI;EACA,IAAM,IAAa,EAAO,cAAc;EACxC,CAAI,CAAC,EAAW,aAAa,EAAW,UAAU,WAAW,OACzD,EAAW,YAAY,CAAC,CAAC,CAAC;EAE9B,KAAK,IAAM,KAAY,EAAW,WAC9B,AAAI,MAAe,OAAM,OAAO,EAAS,aACpC,EAAS,aAAa;EAG/B,OADA,MAAM,EAAO,cAAc,CAAU,GAC9B;CACX,QAAQ;EACJ,OAAO;CACX;AACJ"}
@@ -0,0 +1,2 @@
1
+ const e=require("../hooks/use-document-visibility.cjs"),t=require("../hooks/use-interval.cjs"),n=require("./link-stats.cjs");let r=require("react");function i(i,a={}){let{intervalMs:o=2e3,pauseWhenHidden:s=!0,kind:c}=a,[l,u]=(0,r.useState)(null),d=(0,r.useRef)(null),f=(0,r.useRef)(!1),p=e.useDocumentVisibility(),m=s&&p===`hidden`;return(0,r.useEffect)(()=>{d.current=n.createLinkStatsSampler(c===void 0?{}:{kind:c}),u(null)},[i,c]),(0,r.useEffect)(()=>{m||d.current?.reset()},[m]),t.useInterval(()=>{let e=d.current;i!==null&&e!==null&&i.connectionState===`connected`&&(f.current||(f.current=!0,e.sample(i).then(e=>u(e)).catch(()=>void 0).finally(()=>{f.current=!1})))},i===null||m?null:o),l}exports.useLinkStats=i;
2
+ //# sourceMappingURL=use-link-stats.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-link-stats.cjs","names":[],"sources":["../../src/webrtc/use-link-stats.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\n\nimport { useDocumentVisibility } from \"../hooks/use-document-visibility\";\nimport { useInterval } from \"../hooks/use-interval\";\nimport {\n createLinkStatsSampler,\n type LinkStats,\n type LinkStatsKind,\n type LinkStatsSampler,\n} from \"./link-stats\";\n\n/** Options for {@link useLinkStats}. */\nexport interface UseLinkStatsOptions {\n /**\n * How often to sample, in milliseconds. Default `2000`.\n *\n * `getStats()` walks every report a connection holds, and a mesh runs one\n * per participant — on the device least able to pay for it. The numbers feed\n * a latency badge and a throughput figure nobody reads at 1 Hz, so the\n * default is deliberately slower than a frame budget suggests.\n */\n intervalMs?: number;\n /**\n * Stop sampling while the tab is in the background. Default `true`.\n *\n * The audio path is untouched by this — people in a backgrounded call still\n * expect to hear it. What stops is the measuring, which nobody can see.\n */\n pauseWhenHidden?: boolean;\n /** Which media the throughput counts. Default `\"video\"`. */\n kind?: LinkStatsKind;\n}\n\n/**\n * Poll one connection's throughput, resolution and round trip.\n *\n * Sampling runs only while the connection is `\"connected\"`, so a link that is\n * still negotiating or already gone costs nothing. The last reading is kept\n * across pauses rather than cleared, because a badge that blanks every time the\n * tab loses focus reads as a broken connection.\n *\n * Coming back from a pause resets the baseline first. Without that, the sample\n * after a five-minute background stretch divides five minutes of bytes by five\n * minutes and reports the average of a period the person is not asking about.\n *\n * @param connection - The connection to watch, or `null` before one exists.\n * @param options - See {@link UseLinkStatsOptions}.\n * @returns The latest reading, or `null` before the first sample lands.\n *\n * @example\n * const stats = useLinkStats(pc);\n *\n * return <span>{stats ? `${stats.kbps} kbps · ${stats.rttMs ?? \"—\"} ms` : \"medindo…\"}</span>;\n */\nexport function useLinkStats(\n connection: RTCPeerConnection | null,\n options: UseLinkStatsOptions = {},\n): LinkStats | null {\n const { intervalMs = 2000, pauseWhenHidden = true, kind } = options;\n const [stats, setStats] = useState<LinkStats | null>(null);\n const samplerRef = useRef<LinkStatsSampler | null>(null);\n const inFlightRef = useRef(false);\n const visibility = useDocumentVisibility();\n const hidden = pauseWhenHidden && visibility === \"hidden\";\n\n useEffect(() => {\n samplerRef.current = createLinkStatsSampler(kind === undefined ? {} : { kind });\n setStats(null);\n }, [connection, kind]);\n\n useEffect(() => {\n if (!hidden) samplerRef.current?.reset();\n }, [hidden]);\n\n const paused = connection === null || hidden;\n\n useInterval(\n () => {\n const sampler = samplerRef.current;\n if (connection === null || sampler === null) return;\n if (connection.connectionState !== \"connected\") return;\n if (inFlightRef.current) return;\n inFlightRef.current = true;\n void sampler\n .sample(connection)\n .then((next) => setStats(next))\n .catch(() => undefined)\n .finally(() => {\n inFlightRef.current = false;\n });\n },\n paused ? null : intervalMs,\n );\n\n return stats;\n}\n"],"mappings":"oJAsDA,SAAgB,EACZ,EACA,EAA+B,CAAC,EAChB,CAChB,GAAM,CAAE,aAAa,IAAM,kBAAkB,GAAM,QAAS,EACtD,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAA2B,IAAI,EACnD,GAAA,EAAa,EAAA,OAAA,CAAgC,IAAI,EACjD,GAAA,EAAc,EAAA,OAAA,CAAO,EAAK,EAC1B,EAAa,EAAA,sBAAsB,EACnC,EAAS,GAAmB,IAAe,SA+BjD,OA7BA,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAW,QAAU,EAAA,uBAAuB,IAAS,IAAA,GAAY,CAAC,EAAI,CAAE,MAAK,CAAC,EAC9E,EAAS,IAAI,CACjB,EAAG,CAAC,EAAY,CAAI,CAAC,GAErB,EAAA,EAAA,UAAA,KAAgB,CACP,GAAQ,EAAW,SAAS,MAAM,CAC3C,EAAG,CAAC,CAAM,CAAC,EAIX,EAAA,gBACU,CACF,IAAM,EAAU,EAAW,QACvB,IAAe,MAAQ,IAAY,MACnC,EAAW,kBAAoB,cAC/B,EAAY,UAChB,EAAY,QAAU,GACtB,EACK,OAAO,CAAU,CAAC,CAClB,KAAM,GAAS,EAAS,CAAI,CAAC,CAAC,CAC9B,UAAY,IAAA,EAAS,CAAC,CACtB,YAAc,CACX,EAAY,QAAU,EAC1B,CAAC,GACT,EAhBW,IAAe,MAAQ,EAiBzB,KAAO,CACpB,EAEO,CACX"}
@@ -0,0 +1,22 @@
1
+ import { useDocumentVisibility as e } from "../hooks/use-document-visibility.js";
2
+ import { useInterval as t } from "../hooks/use-interval.js";
3
+ import { createLinkStatsSampler as n } from "./link-stats.js";
4
+ import { useEffect as r, useRef as i, useState as a } from "react";
5
+ //#region src/webrtc/use-link-stats.ts
6
+ function o(o, s = {}) {
7
+ let { intervalMs: c = 2e3, pauseWhenHidden: l = !0, kind: u } = s, [d, f] = a(null), p = i(null), m = i(!1), h = e(), g = l && h === "hidden";
8
+ return r(() => {
9
+ p.current = n(u === void 0 ? {} : { kind: u }), f(null);
10
+ }, [o, u]), r(() => {
11
+ g || p.current?.reset();
12
+ }, [g]), t(() => {
13
+ let e = p.current;
14
+ o !== null && e !== null && o.connectionState === "connected" && (m.current || (m.current = !0, e.sample(o).then((e) => f(e)).catch(() => void 0).finally(() => {
15
+ m.current = !1;
16
+ })));
17
+ }, o === null || g ? null : c), d;
18
+ }
19
+ //#endregion
20
+ export { o as useLinkStats };
21
+
22
+ //# sourceMappingURL=use-link-stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-link-stats.js","names":[],"sources":["../../src/webrtc/use-link-stats.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\n\nimport { useDocumentVisibility } from \"../hooks/use-document-visibility\";\nimport { useInterval } from \"../hooks/use-interval\";\nimport {\n createLinkStatsSampler,\n type LinkStats,\n type LinkStatsKind,\n type LinkStatsSampler,\n} from \"./link-stats\";\n\n/** Options for {@link useLinkStats}. */\nexport interface UseLinkStatsOptions {\n /**\n * How often to sample, in milliseconds. Default `2000`.\n *\n * `getStats()` walks every report a connection holds, and a mesh runs one\n * per participant — on the device least able to pay for it. The numbers feed\n * a latency badge and a throughput figure nobody reads at 1 Hz, so the\n * default is deliberately slower than a frame budget suggests.\n */\n intervalMs?: number;\n /**\n * Stop sampling while the tab is in the background. Default `true`.\n *\n * The audio path is untouched by this — people in a backgrounded call still\n * expect to hear it. What stops is the measuring, which nobody can see.\n */\n pauseWhenHidden?: boolean;\n /** Which media the throughput counts. Default `\"video\"`. */\n kind?: LinkStatsKind;\n}\n\n/**\n * Poll one connection's throughput, resolution and round trip.\n *\n * Sampling runs only while the connection is `\"connected\"`, so a link that is\n * still negotiating or already gone costs nothing. The last reading is kept\n * across pauses rather than cleared, because a badge that blanks every time the\n * tab loses focus reads as a broken connection.\n *\n * Coming back from a pause resets the baseline first. Without that, the sample\n * after a five-minute background stretch divides five minutes of bytes by five\n * minutes and reports the average of a period the person is not asking about.\n *\n * @param connection - The connection to watch, or `null` before one exists.\n * @param options - See {@link UseLinkStatsOptions}.\n * @returns The latest reading, or `null` before the first sample lands.\n *\n * @example\n * const stats = useLinkStats(pc);\n *\n * return <span>{stats ? `${stats.kbps} kbps · ${stats.rttMs ?? \"—\"} ms` : \"medindo…\"}</span>;\n */\nexport function useLinkStats(\n connection: RTCPeerConnection | null,\n options: UseLinkStatsOptions = {},\n): LinkStats | null {\n const { intervalMs = 2000, pauseWhenHidden = true, kind } = options;\n const [stats, setStats] = useState<LinkStats | null>(null);\n const samplerRef = useRef<LinkStatsSampler | null>(null);\n const inFlightRef = useRef(false);\n const visibility = useDocumentVisibility();\n const hidden = pauseWhenHidden && visibility === \"hidden\";\n\n useEffect(() => {\n samplerRef.current = createLinkStatsSampler(kind === undefined ? {} : { kind });\n setStats(null);\n }, [connection, kind]);\n\n useEffect(() => {\n if (!hidden) samplerRef.current?.reset();\n }, [hidden]);\n\n const paused = connection === null || hidden;\n\n useInterval(\n () => {\n const sampler = samplerRef.current;\n if (connection === null || sampler === null) return;\n if (connection.connectionState !== \"connected\") return;\n if (inFlightRef.current) return;\n inFlightRef.current = true;\n void sampler\n .sample(connection)\n .then((next) => setStats(next))\n .catch(() => undefined)\n .finally(() => {\n inFlightRef.current = false;\n });\n },\n paused ? null : intervalMs,\n );\n\n return stats;\n}\n"],"mappings":";;;;;AAsDA,SAAgB,EACZ,GACA,IAA+B,CAAC,GAChB;CAChB,IAAM,EAAE,gBAAa,KAAM,qBAAkB,IAAM,YAAS,GACtD,CAAC,GAAO,KAAY,EAA2B,IAAI,GACnD,IAAa,EAAgC,IAAI,GACjD,IAAc,EAAO,EAAK,GAC1B,IAAa,EAAsB,GACnC,IAAS,KAAmB,MAAe;CA+BjD,OA7BA,QAAgB;EAEZ,AADA,EAAW,UAAU,EAAuB,MAAS,KAAA,IAAY,CAAC,IAAI,EAAE,QAAK,CAAC,GAC9E,EAAS,IAAI;CACjB,GAAG,CAAC,GAAY,CAAI,CAAC,GAErB,QAAgB;EACZ,AAAK,KAAQ,EAAW,SAAS,MAAM;CAC3C,GAAG,CAAC,CAAM,CAAC,GAIX,QACU;EACF,IAAM,IAAU,EAAW;EACvB,MAAe,QAAQ,MAAY,QACnC,EAAW,oBAAoB,gBAC/B,EAAY,YAChB,EAAY,UAAU,IACtB,EACK,OAAO,CAAU,CAAC,CAClB,MAAM,MAAS,EAAS,CAAI,CAAC,CAAC,CAC9B,YAAY,KAAA,CAAS,CAAC,CACtB,cAAc;GACX,EAAY,UAAU;EAC1B,CAAC;CACT,GAhBW,MAAe,QAAQ,IAiBzB,OAAO,CACpB,GAEO;AACX"}
@@ -1,2 +1,2 @@
1
- function e(e){try{return JSON.parse(e)}catch{return e}}function t(t,n={}){let{protocols:r,maxRetries:i=10,initialBackoff:a=1e3,maxBackoff:o=3e4,pingInterval:s=0,pingPayload:c=JSON.stringify({type:`ping`}),respondToPing:l=!0,pongPayload:u=JSON.stringify({type:`pong`}),queueWhileClosed:d=!1,maxQueuedMessages:f=100,parser:p=e,onOpen:m,onMessage:h,onClose:g,onError:_,onStatusChange:v}=n,y=null,b=null,x=null,S=0,C=`idle`,w=!1,T=[];function E(e){return typeof e==`object`&&!!e&&e.type===`ping`}function D(e){for(;T.length>0&&e.readyState===WebSocket.OPEN;)e.send(T.shift())}function O(e){C!==e&&(C=e,v?.(e))}function k(){x&&=(clearInterval(x),null)}function A(){!s||s<=0||(k(),x=setInterval(()=>{y?.readyState===WebSocket.OPEN&&y.send(c)},s))}function j(){if(w)return;if(S>=i){O(`error`);return}let e=Math.min(a*2**S,o);S+=1,b=setTimeout(M,e)}function M(){if(w)return;if(y){let e=y;e.onmessage=null,e.onclose=null,e.onerror=null,e.readyState!==WebSocket.CONNECTING&&(e.onopen=null),F(e)}O(`connecting`);let e=new WebSocket(t,r);y=e,e.onopen=t=>{S=0,O(`open`),A(),D(e),m?.(t)},e.onmessage=t=>{let n=typeof t.data==`string`?t.data:``,r=p(n);l&&E(r)&&e.readyState===WebSocket.OPEN&&e.send(u),h?.({data:r,raw:t})},e.onerror=e=>{_?.(e)},e.onclose=e=>{k(),g?.(e),y=null,O(`closed`),!w&&!e.wasClean&&j()}}function N(e){return y?.readyState===WebSocket.OPEN?(y.send(e),!0):!d||w?!1:(T.length>=f&&T.shift(),T.push(e),!0)}function P(e,t){w=!0,b&&=(clearTimeout(b),null),k(),S=0,T.length=0,y&&=(O(`closing`),F(y,e,t),null),O(`closed`)}function F(e,t,n){if(e.readyState===WebSocket.CONNECTING){e.onopen=()=>e.close(t,n),e.onmessage=null,e.onerror=null,e.onclose=null;return}e.close(t,n)}function I(){b&&=(clearTimeout(b),null),S=0,w=!1,M()}return M(),{send:N,close:P,reconnect:I,get status(){return C}}}exports.createWebSocket=t;
1
+ const e=require("../utils/json-frame.cjs"),t=require("./resilience.cjs");function n(n,r={}){let{protocols:i,maxRetries:a=10,initialBackoff:o=1e3,maxBackoff:s=3e4,jitter:ee=.3,handshakeTimeout:c=8e3,silenceTimeout:l=0,waitForOnline:u=!0,pingInterval:d=0,pingPayload:f=JSON.stringify({type:`ping`}),respondToPing:p=!0,pongPayload:m=JSON.stringify({type:`pong`}),queueWhileClosed:h=!1,maxQueuedMessages:te=100,parser:ne,onOpen:g,onMessage:_,onClose:v,onError:y,onParseError:b,onStatusChange:re,onReconnecting:ie,onReconnected:x,onLost:S}=r,C=null,w=null,T=null,E=null,D=null,O=null,k=l,A=0,j=`idle`,M=!1,N=!1,P=[],F=null,I=null,L=new Promise((e,t)=>{F=e,I=t});L.catch(()=>void 0);function R(e){return typeof e==`object`&&!!e&&e.type===`ping`}function z(e){for(;P.length>0&&e.readyState===WebSocket.OPEN;)e.send(P.shift())}function B(e){j!==e&&(j=e,re?.(e))}function V(){T&&=(clearInterval(T),null)}function H(){E&&=(clearTimeout(E),null)}function U(){D&&=(clearTimeout(D),null)}function W(){!d||d<=0||(V(),T=setInterval(()=>{C?.readyState===WebSocket.OPEN&&C.send(f)},d))}function G(){U(),!(M||k<=0)&&(D=setTimeout(K,k))}function K(){U(),!(M||!C)&&(J(C),C=null,B(`closed`),X())}function q(e){H(),!(M||e.readyState!==WebSocket.CONNECTING)&&(J(e),C===e&&(C=null),B(`closed`),X())}function J(e){e.onopen=null,e.onmessage=null,e.onerror=null,e.onclose=null;try{e.close()}catch{}}function Y(e){if(U(),H(),Z(),e===`rejected`&&(M=!0),B(`error`),!N&&I){let t=I;I=null,F=null,t(Error(`websocket_${e}`))}S?.(e)}function X(){if(M)return;if(A>=a){Y(`exhausted`);return}let e=t.backoffDelay(A,{initialBackoff:o,maxBackoff:s,jitter:ee});if(A+=1,ie?.(A,a),u&&typeof navigator<`u`&&navigator.onLine===!1){ae();return}w=setTimeout(Q,e)}function ae(){if(O||typeof window>`u`)return;let e=()=>{Z(),M||Q()};O=e,window.addEventListener(`online`,e)}function Z(){!O||typeof window>`u`||(window.removeEventListener(`online`,O),O=null)}function Q(){if(M)return;if(w=null,H(),C){let e=C;e.onmessage=null,e.onclose=null,e.onerror=null,e.readyState!==WebSocket.CONNECTING&&(e.onopen=null),$(e)}B(`connecting`);let r=new WebSocket(n,i);C=r,c>0&&(E=setTimeout(()=>q(r),c)),r.onopen=e=>{H();let t=A>0;if(A=0,N=!0,B(`open`),W(),G(),z(r),F){let e=F;F=null,I=null,e()}g?.(e),t&&x?.()},r.onmessage=t=>{G();let n=typeof t.data==`string`?t.data:``,i=e.decodeFrame(n,ne,b,`createWebSocket`);if(!i.delivered)return;let a=i.data;p&&R(a)&&r.readyState===WebSocket.OPEN&&r.send(m),_?.({data:a,raw:t})},r.onerror=e=>{y?.(e)},r.onclose=e=>{if(H(),V(),U(),v?.(e),C=null,B(`closed`),!M){if(t.isRejectionCloseCode(e.code)){Y(`rejected`);return}if(t.shouldRetryClose(e.code,e.wasClean)){X();return}N||Y(`rejected`)}}}function oe(e){return C?.readyState===WebSocket.OPEN?(C.send(e),!0):!h||M?!1:(P.length>=te&&P.shift(),P.push(e),!0)}function se(e,t){if(M=!0,w&&=(clearTimeout(w),null),V(),H(),U(),Z(),A=0,P.length=0,I){let e=I;I=null,F=null,e(Error(`websocket_closed`))}C&&=(B(`closing`),$(C,e,t),null),B(`closed`)}function $(e,t,n){if(e.readyState===WebSocket.CONNECTING){e.onopen=()=>e.close(t,n),e.onmessage=null,e.onerror=null,e.onclose=null;return}e.close(t,n)}function ce(){w&&=(clearTimeout(w),null),Z(),A=0,M=!1,Q()}function le(e){k=Number.isFinite(e)&&e>0?e:0,C?.readyState===WebSocket.OPEN?G():U()}return Q(),{send:oe,close:se,reconnect:ce,setSilenceTimeout:le,opened:L,get status(){return j}}}exports.createWebSocket=n;
2
2
  //# sourceMappingURL=create-web-socket.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-web-socket.cjs","names":[],"sources":["../../src/ws/create-web-socket.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — reconnect with backoff, heartbeat,\n * the send queue that survives a disconnect and the listener set that must be re-\n * attached to each new socket — one connection's lifetime, one closure. The queue\n * and the reconnect timer are the same decision seen twice.\n */\nexport type WebSocketStatus = \"idle\" | \"connecting\" | \"open\" | \"closing\" | \"closed\" | \"error\";\n\nexport interface WebSocketMessage<T> {\n /** Parsed payload — JSON-decoded when possible, raw string otherwise. */\n data: T;\n /** The original `MessageEvent`. */\n raw: MessageEvent;\n}\n\nexport interface CreateWebSocketOptions<T> {\n /** Subprotocol(s) forwarded to the `WebSocket` constructor. */\n protocols?: string | string[];\n /** Max reconnect attempts. Default: 10. Pass 0 to disable. */\n maxRetries?: number;\n /** Initial backoff (ms). Doubles each attempt, capped at `maxBackoff`. Default: 1000. */\n initialBackoff?: number;\n /** Maximum backoff (ms). Default: 30000. */\n maxBackoff?: number;\n /**\n * Ping interval (ms). When set, the client sends `pingPayload` periodically\n * to keep the socket alive. Default: 0 (disabled).\n *\n * Leave it off against a `tempest-fastapi-sdk` server: that server pings on\n * its own and answers a client-sent `{\"type\":\"ping\"}` with nothing, while a\n * strict handler rejects the unknown frame. What it needs from the client\n * is the `pong` reply, which `respondToPing` sends for you.\n */\n pingInterval?: number;\n /** Payload sent on each ping. Default: `JSON.stringify({ type: \"ping\" })`. */\n pingPayload?: string | Blob | BufferSource;\n /**\n * Reply to a server `{\"type\":\"ping\"}` with `pongPayload`. Default: true.\n *\n * `tempest-fastapi-sdk` closes a socket with code `4408` when no `pong`\n * arrives within `WS_HEARTBEAT_TIMEOUT_SECONDS`, so a client that stays\n * silent is dropped once per timeout. The ping is still forwarded to\n * `onMessage` — the reply is sent before your handler runs.\n */\n respondToPing?: boolean;\n /** Payload sent in reply to a server ping. Default: `JSON.stringify({ type: \"pong\" })`. */\n pongPayload?: string | Blob | BufferSource;\n /**\n * Buffer payloads sent while the socket is not open and flush them on the\n * next `open`. Default: false — `send()` returns false and drops.\n *\n * Without it, an action fired during reconnect backoff vanishes and the UI\n * cannot tell \"never sent\" from \"sent and ignored\".\n */\n queueWhileClosed?: boolean;\n /** Cap on buffered payloads when `queueWhileClosed` is on. Default: 100. */\n maxQueuedMessages?: number;\n /** Parse incoming frames. Default: JSON with raw-string fallback. */\n parser?: (raw: string) => T;\n onOpen?: (event: Event) => void;\n onMessage?: (message: WebSocketMessage<T>) => void;\n onClose?: (event: CloseEvent) => void;\n onError?: (event: Event) => void;\n onStatusChange?: (status: WebSocketStatus) => void;\n}\n\nexport interface WebSocketController {\n /** Send a payload over the current connection. No-op when not open. */\n send: (payload: string | Blob | BufferSource) => boolean;\n /** Close the connection and stop reconnecting. */\n close: (code?: number, reason?: string) => void;\n /** Force an immediate reconnect, resetting the retry counter. */\n reconnect: () => void;\n /** Current connection status. */\n readonly status: WebSocketStatus;\n}\n\nfunction defaultParser<T>(raw: string): T {\n try {\n return JSON.parse(raw) as T;\n } catch {\n return raw as unknown as T;\n }\n}\n\n/**\n * Open a WebSocket with automatic exponential-backoff reconnect, optional\n * heartbeat pings, and typed JSON parsing.\n *\n * @param url - Full ws:// or wss:// URL.\n * @param options - Connection configuration and callbacks.\n * @returns Controller exposing `send`, `close`, `reconnect`, and `status`.\n */\nexport function createWebSocket<T = unknown>(\n url: string,\n options: CreateWebSocketOptions<T> = {},\n): WebSocketController {\n const {\n protocols,\n maxRetries = 10,\n initialBackoff = 1000,\n maxBackoff = 30000,\n pingInterval = 0,\n pingPayload = JSON.stringify({ type: \"ping\" }),\n respondToPing = true,\n pongPayload = JSON.stringify({ type: \"pong\" }),\n queueWhileClosed = false,\n maxQueuedMessages = 100,\n parser = defaultParser<T>,\n onOpen,\n onMessage,\n onClose,\n onError,\n onStatusChange,\n } = options;\n\n let socket: WebSocket | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let pingTimer: ReturnType<typeof setInterval> | null = null;\n let retries = 0;\n let status: WebSocketStatus = \"idle\";\n let closed = false;\n const outbox: Array<string | Blob | BufferSource> = [];\n\n /** True for a decoded frame that is the server's heartbeat ping. */\n function isServerPing(data: unknown): boolean {\n return (\n typeof data === \"object\" &&\n data !== null &&\n (data as { type?: unknown }).type === \"ping\"\n );\n }\n\n /** Send everything buffered while the socket was down, oldest first. */\n function flushOutbox(ws: WebSocket): void {\n while (outbox.length > 0 && ws.readyState === WebSocket.OPEN) {\n ws.send(outbox.shift()!);\n }\n }\n\n function setStatus(next: WebSocketStatus): void {\n if (status === next) return;\n status = next;\n onStatusChange?.(next);\n }\n\n function clearPing(): void {\n if (pingTimer) {\n clearInterval(pingTimer);\n pingTimer = null;\n }\n }\n\n function startPing(): void {\n if (!pingInterval || pingInterval <= 0) return;\n clearPing();\n pingTimer = setInterval(() => {\n if (socket?.readyState === WebSocket.OPEN) {\n socket.send(pingPayload);\n }\n }, pingInterval);\n }\n\n function scheduleReconnect(): void {\n if (closed) return;\n if (retries >= maxRetries) {\n setStatus(\"error\");\n return;\n }\n const delay = Math.min(initialBackoff * 2 ** retries, maxBackoff);\n retries += 1;\n retryTimer = setTimeout(connect, delay);\n }\n\n function connect(): void {\n if (closed) return;\n if (socket) {\n const previous = socket;\n previous.onmessage = null;\n previous.onclose = null;\n previous.onerror = null;\n if (previous.readyState !== WebSocket.CONNECTING) previous.onopen = null;\n closeSocket(previous);\n }\n setStatus(\"connecting\");\n\n const ws = new WebSocket(url, protocols);\n socket = ws;\n\n ws.onopen = (event) => {\n retries = 0;\n setStatus(\"open\");\n startPing();\n flushOutbox(ws);\n onOpen?.(event);\n };\n\n ws.onmessage = (event) => {\n const raw = typeof event.data === \"string\" ? event.data : \"\";\n const data = parser(raw);\n if (respondToPing && isServerPing(data) && ws.readyState === WebSocket.OPEN) {\n ws.send(pongPayload);\n }\n onMessage?.({ data, raw: event });\n };\n\n ws.onerror = (event) => {\n onError?.(event);\n };\n\n ws.onclose = (event) => {\n clearPing();\n onClose?.(event);\n socket = null;\n setStatus(\"closed\");\n if (!closed && !event.wasClean) {\n scheduleReconnect();\n }\n };\n }\n\n function send(payload: string | Blob | BufferSource): boolean {\n if (socket?.readyState === WebSocket.OPEN) {\n socket.send(payload);\n return true;\n }\n if (!queueWhileClosed || closed) return false;\n if (outbox.length >= maxQueuedMessages) outbox.shift();\n outbox.push(payload);\n return true;\n }\n\n function close(code?: number, reason?: string): void {\n closed = true;\n if (retryTimer) {\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n clearPing();\n retries = 0;\n outbox.length = 0;\n if (socket) {\n setStatus(\"closing\");\n closeSocket(socket, code, reason);\n socket = null;\n }\n setStatus(\"closed\");\n }\n\n /**\n * Close a socket without the \"closed before the connection is established\"\n * console warning.\n *\n * A socket still in `CONNECTING` cannot be closed cleanly — the browser\n * logs that warning on every attempt. React's StrictMode mounts, unmounts\n * and remounts each component in development, so the first socket is\n * always torn down mid-handshake and the message shows up in every dev\n * session of every app using the hook. Deferring the close to `onopen`\n * costs one round trip and keeps the console usable.\n */\n function closeSocket(ws: WebSocket, code?: number, reason?: string): void {\n if (ws.readyState === WebSocket.CONNECTING) {\n ws.onopen = () => ws.close(code, reason);\n ws.onmessage = null;\n ws.onerror = null;\n ws.onclose = null;\n return;\n }\n ws.close(code, reason);\n }\n\n function reconnect(): void {\n if (retryTimer) {\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n retries = 0;\n closed = false;\n connect();\n }\n\n connect();\n\n return {\n send,\n close,\n reconnect,\n get status() {\n return status;\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"create-web-socket.cjs","names":[],"sources":["../../src/ws/create-web-socket.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — reconnect with backoff, heartbeat,\n * the handshake and silence timers that detect a link which never fails out loud,\n * the send queue that survives a disconnect and the listener set that must be re-\n * attached to each new socket — one connection's lifetime, one closure. The queue\n * and the reconnect timer are the same decision seen twice.\n */\nimport {\n backoffDelay,\n isRejectionCloseCode,\n shouldRetryClose,\n type WebSocketLostReason,\n} from \"./resilience\";\nimport { decodeFrame } from \"../utils/json-frame\";\n\nexport type WebSocketStatus = \"idle\" | \"connecting\" | \"open\" | \"closing\" | \"closed\" | \"error\";\n\nexport interface WebSocketMessage<T> {\n /** Parsed payload — JSON-decoded when possible, raw string otherwise. */\n data: T;\n /** The original `MessageEvent`. */\n raw: MessageEvent;\n}\n\nexport interface CreateWebSocketOptions<T> {\n /** Subprotocol(s) forwarded to the `WebSocket` constructor. */\n protocols?: string | string[];\n /** Max reconnect attempts. Default: 10. Pass 0 to disable. */\n maxRetries?: number;\n /** Initial backoff (ms). Doubles each attempt, capped at `maxBackoff`. Default: 1000. */\n initialBackoff?: number;\n /** Maximum backoff (ms). Default: 30000. */\n maxBackoff?: number;\n /**\n * Fraction of each backoff delay added at random, 0–1. Default: 0.3.\n *\n * Matters when the *server* is what went down: every client retries on the\n * same schedule, so the box comes back up into a synchronized stampede. Pass\n * `0` for a fixed schedule.\n */\n jitter?: number;\n /**\n * How long one handshake may stay in `CONNECTING` before the attempt is\n * abandoned and retried (ms). Default: 8000. Pass 0 to disable.\n *\n * A `WebSocket` that cannot reach its server does not necessarily fail: it\n * sits in `CONNECTING` firing neither `open` nor `close` nor `error`. A retry\n * chain built only on those events stops on its first hung attempt and never\n * moves again — and hung, rather than refused, is precisely how a bad mobile\n * link behaves, which is the case reconnection exists for.\n */\n handshakeTimeout?: number;\n /**\n * Silence tolerated on an open socket before the link is treated as dead (ms).\n * Default: 0 (off).\n *\n * The socket only reports a connection that closes cleanly. A link that dies\n * mid-flight leaves `readyState` at `OPEN` on this side with nothing ever\n * arriving again, so silence is the only symptom available. The timer is\n * re-armed by **any** inbound frame, not just by pings — traffic is traffic.\n *\n * Set it to a comfortable multiple of the server's ping interval (2.5× is a\n * good default) so one dropped ping is not mistaken for an outage. When the\n * server announces its own interval in the handshake, feed that back with\n * {@link WebSocketController.setSilenceTimeout} instead of hard-coding the\n * value on both ends.\n */\n silenceTimeout?: number;\n /**\n * Suspend the retry schedule while `navigator.onLine` is false, and resume on\n * the `online` event. Default: true.\n *\n * Burning retries against a radio that is switched off is how a phone\n * exhausts its budget inside a tunnel and gives up exactly when it comes out\n * the other side.\n */\n waitForOnline?: boolean;\n /**\n * Ping interval (ms). When set, the client sends `pingPayload` periodically\n * to keep the socket alive. Default: 0 (disabled).\n *\n * Leave it off against a `tempest-fastapi-sdk` server: that server pings on\n * its own and answers a client-sent `{\"type\":\"ping\"}` with nothing, while a\n * strict handler rejects the unknown frame. What it needs from the client\n * is the `pong` reply, which `respondToPing` sends for you.\n */\n pingInterval?: number;\n /** Payload sent on each ping. Default: `JSON.stringify({ type: \"ping\" })`. */\n pingPayload?: string | Blob | BufferSource;\n /**\n * Reply to a server `{\"type\":\"ping\"}` with `pongPayload`. Default: true.\n *\n * `tempest-fastapi-sdk` closes a socket with code `4408` when no `pong`\n * arrives within `WS_HEARTBEAT_TIMEOUT_SECONDS`, so a client that stays\n * silent is dropped once per timeout. The ping is still forwarded to\n * `onMessage` — the reply is sent before your handler runs.\n */\n respondToPing?: boolean;\n /** Payload sent in reply to a server ping. Default: `JSON.stringify({ type: \"pong\" })`. */\n pongPayload?: string | Blob | BufferSource;\n /**\n * Buffer payloads sent while the socket is not open and flush them on the\n * next `open`. Default: false — `send()` returns false and drops.\n *\n * Without it, an action fired during reconnect backoff vanishes and the UI\n * cannot tell \"never sent\" from \"sent and ignored\".\n */\n queueWhileClosed?: boolean;\n /** Cap on buffered payloads when `queueWhileClosed` is on. Default: 100. */\n maxQueuedMessages?: number;\n /** Parse incoming frames. Default: JSON with raw-string fallback. */\n parser?: (raw: string) => T;\n /**\n * A frame arrived that is not valid JSON, and no `parser` was supplied.\n *\n * Registering this drops the frame instead of delivering it: the previous\n * behaviour handed `onMessage` the raw `string` announced as your message\n * type, so the failure surfaced later, at the first property access, with\n * nothing left pointing at the parse. Leave it out and that behaviour is\n * kept, with a one-time warning in development builds.\n */\n onParseError?: (error: unknown, raw: string) => void;\n onOpen?: (event: Event) => void;\n onMessage?: (message: WebSocketMessage<T>) => void;\n onClose?: (event: CloseEvent) => void;\n onError?: (event: Event) => void;\n onStatusChange?: (status: WebSocketStatus) => void;\n /**\n * A retry has been scheduled. `attempt` is 1-based, `total` is `maxRetries`.\n *\n * Reconnecting is not an error and reads badly as one: announcing every\n * attempt puts a fresh \"the connection dropped\" in front of someone whose\n * session is in the middle of coming back on its own. Show a quiet\n * reconnecting state here and treat {@link CreateWebSocketOptions.onLost} as\n * the failure.\n */\n onReconnecting?: (attempt: number, total: number) => void;\n /**\n * The socket is back up after at least one retry.\n *\n * Nothing is resumed for you: a server that keys state by connection sees a\n * brand-new client, so this is where the caller re-subscribes, re-joins or\n * refetches whatever the gap invalidated.\n */\n onReconnected?: () => void;\n /**\n * No further attempt will be made — `\"rejected\"` when the server refused the\n * client outright (close code 4400–4499, minus the 4408 heartbeat timeout),\n * `\"exhausted\"` when the schedule ran out.\n *\n * This is the one that deserves UI, because it is the only state the caller\n * can act on: offer a \"try again\" that calls\n * {@link WebSocketController.reconnect}.\n */\n onLost?: (reason: WebSocketLostReason) => void;\n}\n\nexport interface WebSocketController {\n /** Send a payload over the current connection. No-op when not open. */\n send: (payload: string | Blob | BufferSource) => boolean;\n /** Close the connection and stop reconnecting. */\n close: (code?: number, reason?: string) => void;\n /** Force an immediate reconnect, resetting the retry counter. */\n reconnect: () => void;\n /**\n * Change the silence watchdog at runtime, in ms. `0` disables it.\n *\n * For the common case where the server announces its heartbeat interval in\n * the first frame, so the tolerated silence is not hard-coded on both ends:\n *\n * ```ts\n * onMessage: ({ data }) => {\n * if (data.type === \"welcome\") socket.setSilenceTimeout(data.heartbeat_seconds * 2500);\n * }\n * ```\n */\n setSilenceTimeout: (ms: number) => void;\n /**\n * Resolves on the first successful open, rejects when the socket is lost\n * before ever opening.\n *\n * Joining and dropping are different events: a call that never connected has\n * to be reported, while one that dropped mid-session should reconnect\n * quietly. Await this for the join, handle\n * {@link CreateWebSocketOptions.onLost} for the drop. Pair it with\n * `maxRetries: 0` when the first attempt should fail fast instead of\n * spending the whole schedule on a server that is not there.\n */\n opened: Promise<void>;\n /** Current connection status. */\n readonly status: WebSocketStatus;\n}\n\n/**\n * Open a WebSocket that survives a bad network: exponential backoff with jitter,\n * a handshake timeout, a silence watchdog, optional heartbeat pings and typed\n * JSON parsing.\n *\n * Three failure modes are covered that an event-driven retry loop misses on its\n * own, because none of them fire an event: a handshake that hangs instead of\n * failing, an open socket whose link died mid-flight, and a device with its\n * radio off burning the retry budget. See `handshakeTimeout`, `silenceTimeout`\n * and `waitForOnline`.\n *\n * @param url - Full ws:// or wss:// URL.\n * @param options - Connection configuration and callbacks.\n * @returns Controller exposing `send`, `close`, `reconnect`, `setSilenceTimeout`,\n * `opened` and `status`.\n *\n * @example\n * const socket = createWebSocket(url, {\n * silenceTimeout: 75_000,\n * onReconnecting: (n, total) => setBanner(`Reconectando ${n}/${total}…`),\n * onReconnected: () => refetchEverything(),\n * onLost: (reason) => setBanner(reason === \"rejected\" ? \"Acesso negado\" : \"Sem conexão\"),\n * });\n * await socket.opened;\n */\nexport function createWebSocket<T = unknown>(\n url: string,\n options: CreateWebSocketOptions<T> = {},\n): WebSocketController {\n const {\n protocols,\n maxRetries = 10,\n initialBackoff = 1000,\n maxBackoff = 30000,\n jitter = 0.3,\n handshakeTimeout = 8000,\n silenceTimeout = 0,\n waitForOnline = true,\n pingInterval = 0,\n pingPayload = JSON.stringify({ type: \"ping\" }),\n respondToPing = true,\n pongPayload = JSON.stringify({ type: \"pong\" }),\n queueWhileClosed = false,\n maxQueuedMessages = 100,\n parser,\n onOpen,\n onMessage,\n onClose,\n onError,\n onParseError,\n onStatusChange,\n onReconnecting,\n onReconnected,\n onLost,\n } = options;\n\n let socket: WebSocket | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let pingTimer: ReturnType<typeof setInterval> | null = null;\n let handshakeTimer: ReturnType<typeof setTimeout> | null = null;\n let silenceTimer: ReturnType<typeof setTimeout> | null = null;\n let onlineListener: (() => void) | null = null;\n let silenceWindow = silenceTimeout;\n let retries = 0;\n let status: WebSocketStatus = \"idle\";\n let closed = false;\n let everOpened = false;\n const outbox: Array<string | Blob | BufferSource> = [];\n\n let settleOpen: (() => void) | null = null;\n let failOpen: ((error: Error) => void) | null = null;\n const opened = new Promise<void>((resolve, reject) => {\n settleOpen = resolve;\n failOpen = reject;\n });\n opened.catch(() => undefined);\n\n /** True for a decoded frame that is the server's heartbeat ping. */\n function isServerPing(data: unknown): boolean {\n return (\n typeof data === \"object\" &&\n data !== null &&\n (data as { type?: unknown }).type === \"ping\"\n );\n }\n\n /** Send everything buffered while the socket was down, oldest first. */\n function flushOutbox(ws: WebSocket): void {\n while (outbox.length > 0 && ws.readyState === WebSocket.OPEN) {\n ws.send(outbox.shift()!);\n }\n }\n\n function setStatus(next: WebSocketStatus): void {\n if (status === next) return;\n status = next;\n onStatusChange?.(next);\n }\n\n function clearPing(): void {\n if (pingTimer) {\n clearInterval(pingTimer);\n pingTimer = null;\n }\n }\n\n function clearHandshake(): void {\n if (handshakeTimer) {\n clearTimeout(handshakeTimer);\n handshakeTimer = null;\n }\n }\n\n function clearSilence(): void {\n if (silenceTimer) {\n clearTimeout(silenceTimer);\n silenceTimer = null;\n }\n }\n\n function startPing(): void {\n if (!pingInterval || pingInterval <= 0) return;\n clearPing();\n pingTimer = setInterval(() => {\n if (socket?.readyState === WebSocket.OPEN) {\n socket.send(pingPayload);\n }\n }, pingInterval);\n }\n\n /**\n * Restart the silence timer, because something just arrived.\n *\n * Armed off any inbound frame rather than off pongs alone: a busy exchange\n * already proves the link is carrying data, and a protocol whose pings the\n * client never sees would otherwise reconnect in the middle of working\n * traffic.\n */\n function armSilence(): void {\n clearSilence();\n if (closed || silenceWindow <= 0) return;\n silenceTimer = setTimeout(onSilence, silenceWindow);\n }\n\n /**\n * Treat a socket that went quiet as dead and start reconnecting.\n *\n * Handlers are detached before closing so the synthetic `close` does not also\n * schedule a retry — that would advance the backoff twice for one failure and\n * halve the time the connection is given to recover.\n */\n function onSilence(): void {\n clearSilence();\n if (closed || !socket) return;\n detach(socket);\n socket = null;\n setStatus(\"closed\");\n scheduleReconnect();\n }\n\n /**\n * Abandon a handshake that never resolved either way.\n *\n * The socket is closed while still `CONNECTING`, which is the one case the\n * console warns about — and the right trade here, because the alternative is\n * deferring the close to an `open` event that by definition is not coming.\n */\n function abandonHandshake(ws: WebSocket): void {\n clearHandshake();\n if (closed || ws.readyState !== WebSocket.CONNECTING) return;\n detach(ws);\n if (socket === ws) socket = null;\n setStatus(\"closed\");\n scheduleReconnect();\n }\n\n /** Drop every handler and close, so the socket can die without being heard. */\n function detach(ws: WebSocket): void {\n ws.onopen = null;\n ws.onmessage = null;\n ws.onerror = null;\n ws.onclose = null;\n try {\n ws.close();\n } catch {\n /* already unusable — nothing to release and nothing to report */\n }\n }\n\n /** Stop for good, telling the caller which of the two dead ends it is. */\n function lose(reason: WebSocketLostReason): void {\n clearSilence();\n clearHandshake();\n clearNetworkWait();\n if (reason === \"rejected\") closed = true;\n setStatus(\"error\");\n if (!everOpened && failOpen) {\n const reject = failOpen;\n failOpen = null;\n settleOpen = null;\n reject(new Error(`websocket_${reason}`));\n }\n onLost?.(reason);\n }\n\n /**\n * Queue the next attempt, or wait for the network when there is none.\n *\n * While the browser reports no connectivity the schedule is suspended and the\n * `online` event drives the next attempt instead, so a device in a tunnel\n * does not spend its whole budget before coming out the other side.\n */\n function scheduleReconnect(): void {\n if (closed) return;\n if (retries >= maxRetries) {\n lose(\"exhausted\");\n return;\n }\n const delay = backoffDelay(retries, { initialBackoff, maxBackoff, jitter });\n retries += 1;\n onReconnecting?.(retries, maxRetries);\n\n if (waitForOnline && typeof navigator !== \"undefined\" && navigator.onLine === false) {\n waitForNetwork();\n return;\n }\n retryTimer = setTimeout(connect, delay);\n }\n\n function waitForNetwork(): void {\n if (onlineListener || typeof window === \"undefined\") return;\n const listener = (): void => {\n clearNetworkWait();\n if (!closed) connect();\n };\n onlineListener = listener;\n window.addEventListener(\"online\", listener);\n }\n\n function clearNetworkWait(): void {\n if (!onlineListener || typeof window === \"undefined\") return;\n window.removeEventListener(\"online\", onlineListener);\n onlineListener = null;\n }\n\n /**\n * Open a socket, replacing whatever is there.\n *\n * The handshake timer is cleared first because it belongs to the socket\n * being replaced, and it holds a reference to it: left armed, it fires later\n * against a connection nobody is waiting for, clears the *new* socket's\n * timer on its way through, and schedules a retry that drops a connection\n * still in flight. `reconnect()` on a hung socket is the path that reaches\n * it.\n */\n function connect(): void {\n if (closed) return;\n retryTimer = null;\n clearHandshake();\n if (socket) {\n const previous = socket;\n previous.onmessage = null;\n previous.onclose = null;\n previous.onerror = null;\n if (previous.readyState !== WebSocket.CONNECTING) previous.onopen = null;\n closeSocket(previous);\n }\n setStatus(\"connecting\");\n\n const ws = new WebSocket(url, protocols);\n socket = ws;\n\n if (handshakeTimeout > 0) {\n handshakeTimer = setTimeout(() => abandonHandshake(ws), handshakeTimeout);\n }\n\n ws.onopen = (event) => {\n clearHandshake();\n const recovered = retries > 0;\n retries = 0;\n everOpened = true;\n setStatus(\"open\");\n startPing();\n armSilence();\n flushOutbox(ws);\n if (settleOpen) {\n const resolve = settleOpen;\n settleOpen = null;\n failOpen = null;\n resolve();\n }\n onOpen?.(event);\n if (recovered) onReconnected?.();\n };\n\n ws.onmessage = (event) => {\n armSilence();\n const raw = typeof event.data === \"string\" ? event.data : \"\";\n const decoded = decodeFrame<T>(raw, parser, onParseError, \"createWebSocket\");\n if (!decoded.delivered) return;\n const data = decoded.data;\n if (respondToPing && isServerPing(data) && ws.readyState === WebSocket.OPEN) {\n ws.send(pongPayload);\n }\n onMessage?.({ data, raw: event });\n };\n\n ws.onerror = (event) => {\n onError?.(event);\n };\n\n /**\n * Classify the close before deciding anything.\n *\n * Three outcomes, in order: a refusal never gets better by trying again;\n * a died-in-flight or temporarily-unavailable close is retried; an\n * ordinary goodbye (a clean 1000) is the session ending on purpose and\n * deserves no error. The one exception is a goodbye on a socket that\n * never opened — the server hung up during the handshake, which the\n * caller awaiting `opened` has to hear about.\n */\n ws.onclose = (event) => {\n clearHandshake();\n clearPing();\n clearSilence();\n onClose?.(event);\n socket = null;\n setStatus(\"closed\");\n if (closed) return;\n if (isRejectionCloseCode(event.code)) {\n lose(\"rejected\");\n return;\n }\n if (shouldRetryClose(event.code, event.wasClean)) {\n scheduleReconnect();\n return;\n }\n if (!everOpened) lose(\"rejected\");\n };\n }\n\n function send(payload: string | Blob | BufferSource): boolean {\n if (socket?.readyState === WebSocket.OPEN) {\n socket.send(payload);\n return true;\n }\n if (!queueWhileClosed || closed) return false;\n if (outbox.length >= maxQueuedMessages) outbox.shift();\n outbox.push(payload);\n return true;\n }\n\n function close(code?: number, reason?: string): void {\n closed = true;\n if (retryTimer) {\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n clearPing();\n clearHandshake();\n clearSilence();\n clearNetworkWait();\n retries = 0;\n outbox.length = 0;\n if (failOpen) {\n const reject = failOpen;\n failOpen = null;\n settleOpen = null;\n reject(new Error(\"websocket_closed\"));\n }\n if (socket) {\n setStatus(\"closing\");\n closeSocket(socket, code, reason);\n socket = null;\n }\n setStatus(\"closed\");\n }\n\n /**\n * Close a socket without the \"closed before the connection is established\"\n * console warning.\n *\n * A socket still in `CONNECTING` cannot be closed cleanly — the browser\n * logs that warning on every attempt. React's StrictMode mounts, unmounts\n * and remounts each component in development, so the first socket is\n * always torn down mid-handshake and the message shows up in every dev\n * session of every app using the hook. Deferring the close to `onopen`\n * costs one round trip and keeps the console usable.\n */\n function closeSocket(ws: WebSocket, code?: number, reason?: string): void {\n if (ws.readyState === WebSocket.CONNECTING) {\n ws.onopen = () => ws.close(code, reason);\n ws.onmessage = null;\n ws.onerror = null;\n ws.onclose = null;\n return;\n }\n ws.close(code, reason);\n }\n\n function reconnect(): void {\n if (retryTimer) {\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n clearNetworkWait();\n retries = 0;\n closed = false;\n connect();\n }\n\n function setSilenceTimeout(ms: number): void {\n silenceWindow = Number.isFinite(ms) && ms > 0 ? ms : 0;\n if (socket?.readyState === WebSocket.OPEN) armSilence();\n else clearSilence();\n }\n\n connect();\n\n return {\n send,\n close,\n reconnect,\n setSilenceTimeout,\n opened,\n get status() {\n return status;\n },\n };\n}\n"],"mappings":"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"}
@@ -1,87 +1,159 @@
1
+ import { decodeFrame as e } from "../utils/json-frame.js";
2
+ import { backoffDelay as t, isRejectionCloseCode as n, shouldRetryClose as r } from "./resilience.js";
1
3
  //#region src/ws/create-web-socket.ts
2
- function e(e) {
3
- try {
4
- return JSON.parse(e);
5
- } catch {
6
- return e;
7
- }
8
- }
9
- function t(t, n = {}) {
10
- let { protocols: r, maxRetries: i = 10, initialBackoff: a = 1e3, maxBackoff: o = 3e4, pingInterval: s = 0, pingPayload: c = JSON.stringify({ type: "ping" }), respondToPing: l = !0, pongPayload: u = JSON.stringify({ type: "pong" }), queueWhileClosed: d = !1, maxQueuedMessages: f = 100, parser: p = e, onOpen: m, onMessage: h, onClose: g, onError: _, onStatusChange: v } = n, y = null, b = null, x = null, S = 0, C = "idle", w = !1, T = [];
11
- function E(e) {
4
+ function i(i, a = {}) {
5
+ let { protocols: ee, maxRetries: o = 10, initialBackoff: s = 1e3, maxBackoff: c = 3e4, jitter: l = .3, handshakeTimeout: u = 8e3, silenceTimeout: d = 0, waitForOnline: f = !0, pingInterval: p = 0, pingPayload: te = JSON.stringify({ type: "ping" }), respondToPing: ne = !0, pongPayload: re = JSON.stringify({ type: "pong" }), queueWhileClosed: m = !1, maxQueuedMessages: h = 100, parser: g, onOpen: _, onMessage: v, onClose: y, onError: ie, onParseError: ae, onStatusChange: b, onReconnecting: x, onReconnected: S, onLost: C } = a, w = null, T = null, E = null, D = null, O = null, k = null, A = d, j = 0, M = "idle", N = !1, P = !1, F = [], I = null, L = null, R = new Promise((e, t) => {
6
+ I = e, L = t;
7
+ });
8
+ R.catch(() => void 0);
9
+ function z(e) {
12
10
  return typeof e == "object" && !!e && e.type === "ping";
13
11
  }
14
- function D(e) {
15
- for (; T.length > 0 && e.readyState === WebSocket.OPEN;) e.send(T.shift());
12
+ function B(e) {
13
+ for (; F.length > 0 && e.readyState === WebSocket.OPEN;) e.send(F.shift());
14
+ }
15
+ function V(e) {
16
+ M !== e && (M = e, b?.(e));
17
+ }
18
+ function H() {
19
+ E &&= (clearInterval(E), null);
20
+ }
21
+ function U() {
22
+ D &&= (clearTimeout(D), null);
23
+ }
24
+ function W() {
25
+ O &&= (clearTimeout(O), null);
26
+ }
27
+ function G() {
28
+ !p || p <= 0 || (H(), E = setInterval(() => {
29
+ w?.readyState === WebSocket.OPEN && w.send(te);
30
+ }, p));
31
+ }
32
+ function K() {
33
+ W(), !(N || A <= 0) && (O = setTimeout(q, A));
34
+ }
35
+ function q() {
36
+ W(), !(N || !w) && (J(w), w = null, V("closed"), X());
16
37
  }
17
- function O(e) {
18
- C !== e && (C = e, v?.(e));
38
+ function oe(e) {
39
+ U(), !(N || e.readyState !== WebSocket.CONNECTING) && (J(e), w === e && (w = null), V("closed"), X());
19
40
  }
20
- function k() {
21
- x &&= (clearInterval(x), null);
41
+ function J(e) {
42
+ e.onopen = null, e.onmessage = null, e.onerror = null, e.onclose = null;
43
+ try {
44
+ e.close();
45
+ } catch {}
22
46
  }
23
- function A() {
24
- !s || s <= 0 || (k(), x = setInterval(() => {
25
- y?.readyState === WebSocket.OPEN && y.send(c);
26
- }, s));
47
+ function Y(e) {
48
+ if (W(), U(), Z(), e === "rejected" && (N = !0), V("error"), !P && L) {
49
+ let t = L;
50
+ L = null, I = null, t(/* @__PURE__ */ Error(`websocket_${e}`));
51
+ }
52
+ C?.(e);
27
53
  }
28
- function j() {
29
- if (w) return;
30
- if (S >= i) {
31
- O("error");
54
+ function X() {
55
+ if (N) return;
56
+ if (j >= o) {
57
+ Y("exhausted");
32
58
  return;
33
59
  }
34
- let e = Math.min(a * 2 ** S, o);
35
- S += 1, b = setTimeout(M, e);
36
- }
37
- function M() {
38
- if (w) return;
39
- if (y) {
40
- let e = y;
41
- e.onmessage = null, e.onclose = null, e.onerror = null, e.readyState !== WebSocket.CONNECTING && (e.onopen = null), F(e);
60
+ let e = t(j, {
61
+ initialBackoff: s,
62
+ maxBackoff: c,
63
+ jitter: l
64
+ });
65
+ if (j += 1, x?.(j, o), f && typeof navigator < "u" && navigator.onLine === !1) {
66
+ se();
67
+ return;
42
68
  }
43
- O("connecting");
44
- let e = new WebSocket(t, r);
45
- y = e, e.onopen = (t) => {
46
- S = 0, O("open"), A(), D(e), m?.(t);
47
- }, e.onmessage = (t) => {
48
- let n = typeof t.data == "string" ? t.data : "", r = p(n);
49
- l && E(r) && e.readyState === WebSocket.OPEN && e.send(u), h?.({
50
- data: r,
51
- raw: t
69
+ T = setTimeout(Q, e);
70
+ }
71
+ function se() {
72
+ if (k || typeof window > "u") return;
73
+ let e = () => {
74
+ Z(), N || Q();
75
+ };
76
+ k = e, window.addEventListener("online", e);
77
+ }
78
+ function Z() {
79
+ !k || typeof window > "u" || (window.removeEventListener("online", k), k = null);
80
+ }
81
+ function Q() {
82
+ if (N) return;
83
+ if (T = null, U(), w) {
84
+ let e = w;
85
+ e.onmessage = null, e.onclose = null, e.onerror = null, e.readyState !== WebSocket.CONNECTING && (e.onopen = null), $(e);
86
+ }
87
+ V("connecting");
88
+ let t = new WebSocket(i, ee);
89
+ w = t, u > 0 && (D = setTimeout(() => oe(t), u)), t.onopen = (e) => {
90
+ U();
91
+ let n = j > 0;
92
+ if (j = 0, P = !0, V("open"), G(), K(), B(t), I) {
93
+ let e = I;
94
+ I = null, L = null, e();
95
+ }
96
+ _?.(e), n && S?.();
97
+ }, t.onmessage = (n) => {
98
+ K();
99
+ let r = typeof n.data == "string" ? n.data : "", i = e(r, g, ae, "createWebSocket");
100
+ if (!i.delivered) return;
101
+ let a = i.data;
102
+ ne && z(a) && t.readyState === WebSocket.OPEN && t.send(re), v?.({
103
+ data: a,
104
+ raw: n
52
105
  });
53
- }, e.onerror = (e) => {
54
- _?.(e);
55
- }, e.onclose = (e) => {
56
- k(), g?.(e), y = null, O("closed"), !w && !e.wasClean && j();
106
+ }, t.onerror = (e) => {
107
+ ie?.(e);
108
+ }, t.onclose = (e) => {
109
+ if (U(), H(), W(), y?.(e), w = null, V("closed"), !N) {
110
+ if (n(e.code)) {
111
+ Y("rejected");
112
+ return;
113
+ }
114
+ if (r(e.code, e.wasClean)) {
115
+ X();
116
+ return;
117
+ }
118
+ P || Y("rejected");
119
+ }
57
120
  };
58
121
  }
59
- function N(e) {
60
- return y?.readyState === WebSocket.OPEN ? (y.send(e), !0) : !d || w ? !1 : (T.length >= f && T.shift(), T.push(e), !0);
122
+ function ce(e) {
123
+ return w?.readyState === WebSocket.OPEN ? (w.send(e), !0) : !m || N ? !1 : (F.length >= h && F.shift(), F.push(e), !0);
61
124
  }
62
- function P(e, t) {
63
- w = !0, b &&= (clearTimeout(b), null), k(), S = 0, T.length = 0, y &&= (O("closing"), F(y, e, t), null), O("closed");
125
+ function le(e, t) {
126
+ if (N = !0, T &&= (clearTimeout(T), null), H(), U(), W(), Z(), j = 0, F.length = 0, L) {
127
+ let e = L;
128
+ L = null, I = null, e(/* @__PURE__ */ Error("websocket_closed"));
129
+ }
130
+ w &&= (V("closing"), $(w, e, t), null), V("closed");
64
131
  }
65
- function F(e, t, n) {
132
+ function $(e, t, n) {
66
133
  if (e.readyState === WebSocket.CONNECTING) {
67
134
  e.onopen = () => e.close(t, n), e.onmessage = null, e.onerror = null, e.onclose = null;
68
135
  return;
69
136
  }
70
137
  e.close(t, n);
71
138
  }
72
- function I() {
73
- b &&= (clearTimeout(b), null), S = 0, w = !1, M();
139
+ function ue() {
140
+ T &&= (clearTimeout(T), null), Z(), j = 0, N = !1, Q();
141
+ }
142
+ function de(e) {
143
+ A = Number.isFinite(e) && e > 0 ? e : 0, w?.readyState === WebSocket.OPEN ? K() : W();
74
144
  }
75
- return M(), {
76
- send: N,
77
- close: P,
78
- reconnect: I,
145
+ return Q(), {
146
+ send: ce,
147
+ close: le,
148
+ reconnect: ue,
149
+ setSilenceTimeout: de,
150
+ opened: R,
79
151
  get status() {
80
- return C;
152
+ return M;
81
153
  }
82
154
  };
83
155
  }
84
156
  //#endregion
85
- export { t as createWebSocket };
157
+ export { i as createWebSocket };
86
158
 
87
159
  //# sourceMappingURL=create-web-socket.js.map