@dylanpiercey/work 0.1.1 → 0.2.1

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 (486) hide show
  1. package/README.md +28 -0
  2. package/bin/work.mjs +244 -4
  3. package/dist/{_Dzz52ZtI.js → _KRMk2duX.js} +378 -12
  4. package/dist/_ZwD1NY8D.js +2 -0
  5. package/dist/_h48l7A55.js +169 -0
  6. package/dist/index.mjs +8621 -7550
  7. package/dist/public/assets/_0vrYNi0q.js +1 -0
  8. package/dist/public/assets/{_DvKnakqk.js → _1nwEHbOU.js} +1 -1
  9. package/dist/public/assets/_1nwEHbOU.js.br +0 -0
  10. package/dist/public/assets/_3qg1gNHu.js +1 -0
  11. package/dist/public/assets/_6_cAsPFk.js +1 -0
  12. package/dist/public/assets/_8mmvS8Ic.js +1 -0
  13. package/dist/public/assets/{_DX5u6Wvm.js → _9s8_FJP0.js} +1 -1
  14. package/dist/public/assets/_9s8_FJP0.js.br +0 -0
  15. package/dist/public/assets/_B0cZNayr.js +1 -0
  16. package/dist/public/assets/_B23wGSFP.js +1 -0
  17. package/dist/public/assets/_B4BVKmh0.js +1 -0
  18. package/dist/public/assets/_B6oF-RIK.js +1 -0
  19. package/dist/public/assets/_B7SrX3Jp.js +1 -0
  20. package/dist/public/assets/{_BzGHediB.js → _B7cXT0VU.js} +1 -1
  21. package/dist/public/assets/_B7cXT0VU.js.br +0 -0
  22. package/dist/public/assets/{_DcOkTFth.js → _B8zhI-xd.js} +1 -1
  23. package/dist/public/assets/_B9epMiSr.js +1 -0
  24. package/dist/public/assets/{_B1D0PIz3.js → _BAYrWdA-.js} +1 -1
  25. package/dist/public/assets/_BAYrWdA-.js.br +0 -0
  26. package/dist/public/assets/{_pXG8fRON.js → _BAeMnj2l.js} +1 -1
  27. package/dist/public/assets/_BAeMnj2l.js.br +0 -0
  28. package/dist/public/assets/_BAs5Ke57.js +1 -0
  29. package/dist/public/assets/{_DomK7Scb.js → _BDkvoJS4.js} +1 -1
  30. package/dist/public/assets/_BDkvoJS4.js.br +0 -0
  31. package/dist/public/assets/{_TrcWRD8T.js → _BE2H3NPA.js} +7 -7
  32. package/dist/public/assets/_BE2H3NPA.js.br +0 -0
  33. package/dist/public/assets/{_-5UNb9-C.js → _BEL3zCP_.js} +1 -1
  34. package/dist/public/assets/_BEL3zCP_.js.br +0 -0
  35. package/dist/public/assets/{_FlITqTCo.js → _BGYXkARu.js} +1 -1
  36. package/dist/public/assets/_BGYXkARu.js.br +0 -0
  37. package/dist/public/assets/{_CxA3Yab0.js → _BHL0M4hx.js} +1 -1
  38. package/dist/public/assets/_BHL0M4hx.js.br +0 -0
  39. package/dist/public/assets/{_1JXC9nFp.js → _BIN7xPBA.js} +1 -1
  40. package/dist/public/assets/_BIN7xPBA.js.br +0 -0
  41. package/dist/public/assets/{_CFUREq1M.js → _BPa71So4.js} +1 -1
  42. package/dist/public/assets/_BPa71So4.js.br +0 -0
  43. package/dist/public/assets/_BQgqTOA6.js +1 -0
  44. package/dist/public/assets/_BVOSK21c.js +1 -0
  45. package/dist/public/assets/{_BVQosHjY.js → _BWdb49DN.js} +1 -1
  46. package/dist/public/assets/_BWdb49DN.js.br +0 -0
  47. package/dist/public/assets/{_C-EkQeP_.js → _BZMc12pa.js} +1 -1
  48. package/dist/public/assets/_BZMc12pa.js.br +0 -0
  49. package/dist/public/assets/{_BkcPY2j0.js → _BcS532Mh.js} +1 -1
  50. package/dist/public/assets/_BcS532Mh.js.br +0 -0
  51. package/dist/public/assets/_Bd3RfEz5.js +1 -0
  52. package/dist/public/assets/{_yUmRJIJ6.js → _BdS-SIP5.js} +1 -1
  53. package/dist/public/assets/_BdS-SIP5.js.br +0 -0
  54. package/dist/public/assets/{_B3NfNm8M.js → _BfU4-P5_.js} +13 -13
  55. package/dist/public/assets/_BfU4-P5_.js.br +0 -0
  56. package/dist/public/assets/{_DUs3ZGbj.js → _BidDqDX-.js} +1 -1
  57. package/dist/public/assets/_BidDqDX-.js.br +0 -0
  58. package/dist/public/assets/{_Cx_aJfDp.js → _Blw8tTg6.js} +1 -1
  59. package/dist/public/assets/_Blw8tTg6.js.br +0 -0
  60. package/dist/public/assets/{_CfGEGt_k.js → _BnIxui8m.js} +1 -1
  61. package/dist/public/assets/_BnIxui8m.js.br +0 -0
  62. package/dist/public/assets/{_DRHCy0UF.js → _BnnnKUB5.js} +1 -1
  63. package/dist/public/assets/_BnnnKUB5.js.br +0 -0
  64. package/dist/public/assets/_BoFnbJY1.js +1 -0
  65. package/dist/public/assets/{_Cb_k_JA_.js → _BotYlEwM.js} +1 -1
  66. package/dist/public/assets/_BotYlEwM.js.br +0 -0
  67. package/dist/public/assets/_BphDw0sE.js +1 -0
  68. package/dist/public/assets/_BqeBwjz2.js +1 -0
  69. package/dist/public/assets/{_BtZ2AatF.js → _Bs2mrt6D.js} +1 -1
  70. package/dist/public/assets/{_BRQ6iQ5p.js → _BtNwLErW.js} +1 -1
  71. package/dist/public/assets/_BtNwLErW.js.br +0 -0
  72. package/dist/public/assets/_Bv7YPgSd.js +1 -0
  73. package/dist/public/assets/{_CWG813ur.js → _BvrmMS2E.js} +1 -1
  74. package/dist/public/assets/_BvrmMS2E.js.br +0 -0
  75. package/dist/public/assets/{_DgWKSW-Y.js → _BwX_PmTl.js} +1 -1
  76. package/dist/public/assets/_BwX_PmTl.js.br +0 -0
  77. package/dist/public/assets/{_DEOryr7w.js → _C05-oJk8.js} +1 -1
  78. package/dist/public/assets/_C05-oJk8.js.br +0 -0
  79. package/dist/public/assets/{_CCVkYcN5.js → _C2yU1dLq.js} +1 -1
  80. package/dist/public/assets/_C2yU1dLq.js.br +0 -0
  81. package/dist/public/assets/{_CKLGAGKJ.js → _C2z40jpl.js} +1 -1
  82. package/dist/public/assets/_C2z40jpl.js.br +0 -0
  83. package/dist/public/assets/_C6HTijVG.js +1 -0
  84. package/dist/public/assets/{_1xOByopP.js → _C6lJdZzj.js} +1 -1
  85. package/dist/public/assets/_C6lJdZzj.js.br +0 -0
  86. package/dist/public/assets/_C7vMBzAL.js +1 -0
  87. package/dist/public/assets/_C7vMBzAL.js.br +0 -0
  88. package/dist/public/assets/{_B_0C2_RO.js → _C8pXYUZJ.js} +1 -1
  89. package/dist/public/assets/_C8pXYUZJ.js.br +0 -0
  90. package/dist/public/assets/_C9zPmOH2.js +2 -0
  91. package/dist/public/assets/_C9zPmOH2.js.br +0 -0
  92. package/dist/public/assets/_CA7mM4qX.js +1 -0
  93. package/dist/public/assets/{_DM60dKRs.js → _CBENFdjy.js} +1 -1
  94. package/dist/public/assets/_CBENFdjy.js.br +0 -0
  95. package/dist/public/assets/_CDlUndl3.js +1 -0
  96. package/dist/public/assets/_CDlUndl3.js.br +0 -0
  97. package/dist/public/assets/_CEPIxYBN.js +86 -0
  98. package/dist/public/assets/_CEPIxYBN.js.br +0 -0
  99. package/dist/public/assets/{_B7VYILLZ.js → _CFHivO5W.js} +1 -1
  100. package/dist/public/assets/_CFHivO5W.js.br +0 -0
  101. package/dist/public/assets/{_C98JZr09.js → _CGylDsvz.js} +1 -1
  102. package/dist/public/assets/_CGylDsvz.js.br +0 -0
  103. package/dist/public/assets/_CISImxw1.js +1 -0
  104. package/dist/public/assets/{_m2tRa0jk.js → _CJAgrixe.js} +1 -1
  105. package/dist/public/assets/_CJAgrixe.js.br +0 -0
  106. package/dist/public/assets/_CJEPkQp1.js +1 -0
  107. package/dist/public/assets/_CLF8kSpu.js +1 -0
  108. package/dist/public/assets/{_DxD_AC3a.js → _CMe89-TR.js} +1 -1
  109. package/dist/public/assets/_CMe89-TR.js.br +0 -0
  110. package/dist/public/assets/{_CY87Y7QL.js → _CQl6IKdI.js} +1 -1
  111. package/dist/public/assets/_CQl6IKdI.js.br +0 -0
  112. package/dist/public/assets/_CR1lhMQ3.js +1 -0
  113. package/dist/public/assets/_CRBiqW8E.js +1 -0
  114. package/dist/public/assets/{_UaKPY71M.js → _CRvIHvZK.js} +1 -1
  115. package/dist/public/assets/_CRvIHvZK.js.br +0 -0
  116. package/dist/public/assets/_CT-EgIiU.js +1 -0
  117. package/dist/public/assets/_CVXUwEfP.js +1 -0
  118. package/dist/public/assets/_CW5A7nA-.js +1 -0
  119. package/dist/public/assets/{_Do1ltY2a.js → _CWuvWHgA.js} +1 -1
  120. package/dist/public/assets/_CWuvWHgA.js.br +0 -0
  121. package/dist/public/assets/{_CfdK8EeQ.js → _CXSkYGoG.js} +1 -1
  122. package/dist/public/assets/_CXSkYGoG.js.br +0 -0
  123. package/dist/public/assets/_CYtwao4w.js +1 -0
  124. package/dist/public/assets/{_CUsrMpdL.js → _CZTHGPNy.js} +1 -1
  125. package/dist/public/assets/_CZTHGPNy.js.br +0 -0
  126. package/dist/public/assets/{_HM_QqtJi.js → _CappgGwi.js} +1 -1
  127. package/dist/public/assets/_CappgGwi.js.br +0 -0
  128. package/dist/public/assets/_CeARoEjl.js +1 -0
  129. package/dist/public/assets/_CeARoEjl.js.br +0 -0
  130. package/dist/public/assets/{_BDUjU8Ev.js → _Cf7js-r0.js} +1 -1
  131. package/dist/public/assets/{_Dbm6zAQS.js → _ChJB1hf1.js} +1 -1
  132. package/dist/public/assets/_ChJB1hf1.js.br +0 -0
  133. package/dist/public/assets/_ChkvWPVl.js +1 -0
  134. package/dist/public/assets/{_C0Ib7MZF.js → _CkLUNG6d.js} +1 -1
  135. package/dist/public/assets/_CkLUNG6d.js.br +0 -0
  136. package/dist/public/assets/_Cm2gzAKb.js +1 -0
  137. package/dist/public/assets/_CmCI2k_X.js +1 -0
  138. package/dist/public/assets/{_D9r_Fn0I.js → _CnIFQPqM.js} +1 -1
  139. package/dist/public/assets/_CnIFQPqM.js.br +0 -0
  140. package/dist/public/assets/_CpzkPBcd.js +1 -0
  141. package/dist/public/assets/{_DL3w8c_p.js → _Cqij7P1t.js} +1 -1
  142. package/dist/public/assets/_Cqij7P1t.js.br +0 -0
  143. package/dist/public/assets/_CsGBY_OR.js +1 -0
  144. package/dist/public/assets/_Cvq7oyHi.js +1 -0
  145. package/dist/public/assets/_CxQnabfk.js +6 -0
  146. package/dist/public/assets/_CxQnabfk.js.br +0 -0
  147. package/dist/public/assets/_Cxuln-KJ.js +1 -0
  148. package/dist/public/assets/{_DifjKObE.js → _CzjTMNNg.js} +1 -1
  149. package/dist/public/assets/_CzjTMNNg.js.br +0 -0
  150. package/dist/public/assets/{_CiYQhf81.js → _D-ewgB-g.js} +1 -1
  151. package/dist/public/assets/_D-ewgB-g.js.br +0 -0
  152. package/dist/public/assets/_D-lSuOqN.js +1 -0
  153. package/dist/public/assets/{_kWQ0dtYZ.js → _D0toMFV3.js} +1 -1
  154. package/dist/public/assets/_D0toMFV3.js.br +0 -0
  155. package/dist/public/assets/{_JEvx8ub4.js → _D1SAzhRo.js} +1 -1
  156. package/dist/public/assets/_D1SAzhRo.js.br +0 -0
  157. package/dist/public/assets/{_ClHPjF-1.js → _D2NPwpCK.js} +1 -1
  158. package/dist/public/assets/_D2NPwpCK.js.br +0 -0
  159. package/dist/public/assets/_D2pdXUO3.js +1 -0
  160. package/dist/public/assets/{_DzKW89Sx.js → _D36AHDIn.js} +1 -1
  161. package/dist/public/assets/_D36AHDIn.js.br +0 -0
  162. package/dist/public/assets/{_EefWj858.js → _D39T9Hc3.js} +1 -1
  163. package/dist/public/assets/_D39T9Hc3.js.br +0 -0
  164. package/dist/public/assets/{_DJqEBkNd.js → _D4iaGiBC.js} +1 -1
  165. package/dist/public/assets/_D4iaGiBC.js.br +0 -0
  166. package/dist/public/assets/{_BPSTqkJn.js → _D60_NLSK.js} +1 -1
  167. package/dist/public/assets/_D60_NLSK.js.br +0 -0
  168. package/dist/public/assets/{_Di1ccq_2.js → _D6O591m8.js} +1 -1
  169. package/dist/public/assets/_D6O591m8.js.br +0 -0
  170. package/dist/public/assets/_D6OrE8WN.js +1 -0
  171. package/dist/public/assets/_D6b9irVv.js +1 -0
  172. package/dist/public/assets/_D7e9Ia7P.js +1 -0
  173. package/dist/public/assets/_D95nSKbk.js +1 -0
  174. package/dist/public/assets/_D9qcWm-q.js +1 -0
  175. package/dist/public/assets/_D9uiExWV.js +1 -0
  176. package/dist/public/assets/{__cyRVmTO.js → _DAPIaEl7.js} +1 -1
  177. package/dist/public/assets/_DAPIaEl7.js.br +0 -0
  178. package/dist/public/assets/_DBOZe4SW.js +1 -0
  179. package/dist/public/assets/{_Cmmdd2e3.js → _DCfgIXDZ.js} +1 -1
  180. package/dist/public/assets/_DCfgIXDZ.js.br +0 -0
  181. package/dist/public/assets/_DDrn7T44.js +1 -0
  182. package/dist/public/assets/{_DQyjuXTi.js → _DDs3ube1.js} +1 -1
  183. package/dist/public/assets/_DDs3ube1.js.br +0 -0
  184. package/dist/public/assets/_DEKJL8_K.js +1 -0
  185. package/dist/public/assets/_DJOVMSj1.js +1 -0
  186. package/dist/public/assets/{_CqCenHOp.js → _DJSmALSy.js} +1 -1
  187. package/dist/public/assets/_DJSmALSy.js.br +0 -0
  188. package/dist/public/assets/_DLFpqsl0.js +1 -0
  189. package/dist/public/assets/_DOyEaX0U.js +1 -0
  190. package/dist/public/assets/{_B1-W0Sn2.js → _DQd4Gai0.js} +1 -1
  191. package/dist/public/assets/_DQd4Gai0.js.br +0 -0
  192. package/dist/public/assets/_DQlvpXhI.js +1 -0
  193. package/dist/public/assets/_DQlvpXhI.js.br +0 -0
  194. package/dist/public/assets/{_DZvkgS3V.js → _DRTaehFU.js} +1 -1
  195. package/dist/public/assets/_DRTaehFU.js.br +0 -0
  196. package/dist/public/assets/_DTKyMAAU.js +1 -0
  197. package/dist/public/assets/{_3aBl7SEn.js → _DWuVY-5K.js} +1 -1
  198. package/dist/public/assets/_DWuVY-5K.js.br +0 -0
  199. package/dist/public/assets/_DXAMCgpo.js +1 -0
  200. package/dist/public/assets/_DXAMCgpo.js.br +0 -0
  201. package/dist/public/assets/{_BSWN-v3P.js → _DXBIA5Jf.js} +1 -1
  202. package/dist/public/assets/_DXBIA5Jf.js.br +0 -0
  203. package/dist/public/assets/_Da14xaff.js +1 -0
  204. package/dist/public/assets/_Da14xaff.js.br +0 -0
  205. package/dist/public/assets/_DbXR2mlf.js +1 -0
  206. package/dist/public/assets/{_HmkI4Rfn.js → _Dbp_FIBr.js} +1 -1
  207. package/dist/public/assets/_Dbp_FIBr.js.br +0 -0
  208. package/dist/public/assets/_Dc_Ittsm.js +1 -0
  209. package/dist/public/assets/_DdYt8SWd.js +1 -0
  210. package/dist/public/assets/{_vcLBrU2m.js → _DgWkLH_d.js} +1 -1
  211. package/dist/public/assets/_DgWkLH_d.js.br +0 -0
  212. package/dist/public/assets/_Dh5xTE2G.js +1 -0
  213. package/dist/public/assets/{_CDY0RpYX.js → _DiE4MtP8.js} +1 -1
  214. package/dist/public/assets/_DiE4MtP8.js.br +0 -0
  215. package/dist/public/assets/_DjGr7GRl.js +1 -0
  216. package/dist/public/assets/_DjGr7GRl.js.br +0 -0
  217. package/dist/public/assets/_DotQ0YLh.js +2 -0
  218. package/dist/public/assets/_DotQ0YLh.js.br +0 -0
  219. package/dist/public/assets/_DqAfN3OQ.js +1 -0
  220. package/dist/public/assets/{_BKIx7IZP.js → _DqMNzDjo.js} +1 -1
  221. package/dist/public/assets/_DqMNzDjo.js.br +0 -0
  222. package/dist/public/assets/{_BsJZPHmz.js → _Dqqxf-zK.js} +1 -1
  223. package/dist/public/assets/_Dqqxf-zK.js.br +0 -0
  224. package/dist/public/assets/{_dsj7vMTW.js → _Dufbr-KL.js} +1 -1
  225. package/dist/public/assets/_Dufbr-KL.js.br +0 -0
  226. package/dist/public/assets/_Dv-q-Ej7.js +1 -0
  227. package/dist/public/assets/{_BHU5aikZ.js → _Dvfi_oP5.js} +1 -1
  228. package/dist/public/assets/_DvyJx5yW.js +1 -0
  229. package/dist/public/assets/_DvyJx5yW.js.br +0 -0
  230. package/dist/public/assets/_DxMb2QV8.js +1 -0
  231. package/dist/public/assets/{_BKHtiW96.js → _DyYpJ-H0.js} +1 -1
  232. package/dist/public/assets/_DyYpJ-H0.js.br +0 -0
  233. package/dist/public/assets/_DyhlHfZJ.js +1 -0
  234. package/dist/public/assets/_Dz053N5t.js +1 -0
  235. package/dist/public/assets/{_BLfzQY-3.js → _L54I8aS4.js} +1 -1
  236. package/dist/public/assets/_L54I8aS4.js.br +0 -0
  237. package/dist/public/assets/_Ly-2Xkdk.js +1 -0
  238. package/dist/public/assets/{_BSFJEuJX.js → _NN0jQCcG.js} +1 -1
  239. package/dist/public/assets/_NN0jQCcG.js.br +0 -0
  240. package/dist/public/assets/{_7YrArFMB.js → _OzsIhJyG.js} +1 -1
  241. package/dist/public/assets/_OzsIhJyG.js.br +0 -0
  242. package/dist/public/assets/_QJUmfZ1o.js +1 -0
  243. package/dist/public/assets/{_iutZ4EDe.js → _VdeOc79W.js} +1 -1
  244. package/dist/public/assets/_VdeOc79W.js.br +0 -0
  245. package/dist/public/assets/_VkEFwEn-.js +1 -0
  246. package/dist/public/assets/_ahp1rb5o.js +1 -0
  247. package/dist/public/assets/{_VPLoEBTS.js → _aqBpT1uJ.js} +1 -1
  248. package/dist/public/assets/{_DBD57l1T.js → _bgu_OKRO.js} +1 -1
  249. package/dist/public/assets/_bgu_OKRO.js.br +0 -0
  250. package/dist/public/assets/{_C81nSl09.js → _fqHAtYJz.js} +1 -1
  251. package/dist/public/assets/_fqHAtYJz.js.br +0 -0
  252. package/dist/public/assets/_hdoOlF1z.js +1 -0
  253. package/dist/public/assets/{_CtqGSS84.js → _iFV4welK.js} +1 -1
  254. package/dist/public/assets/_iFV4welK.js.br +0 -0
  255. package/dist/public/assets/_ihgadrBp.js +1 -0
  256. package/dist/public/assets/_lJgrZjR9.js +1 -0
  257. package/dist/public/assets/_lJgrZjR9.js.br +0 -0
  258. package/dist/public/assets/_oNShN50G.js +1 -0
  259. package/dist/public/assets/{_C8bqYk4J.js → _oR1qIlrw.js} +1 -1
  260. package/dist/public/assets/_oR1qIlrw.js.br +0 -0
  261. package/dist/public/assets/_pxC2PRJZ.js +1 -0
  262. package/dist/public/assets/_q6iLcLGY.js +1 -0
  263. package/dist/public/assets/_qnKDgwVg.js +1 -0
  264. package/dist/public/assets/_rYWHy2XX.js +1 -0
  265. package/dist/public/assets/{_CHdpLB95.js → _snlh9j-I.js} +1 -1
  266. package/dist/public/assets/_snlh9j-I.js.br +0 -0
  267. package/dist/public/assets/_uGH3cvIZ.js +1 -0
  268. package/dist/public/assets/_uRhnGFfv.js +1 -0
  269. package/dist/public/assets/{_DMJHTH74.js → _wOX1zfjU.js} +1 -1
  270. package/dist/public/assets/_wOX1zfjU.js.br +0 -0
  271. package/dist/public/assets/_xMc_vDaT.js +1 -0
  272. package/dist/public/assets/{_DSUBv-Bl.js → _zGPxyhe6.js} +1 -1
  273. package/dist/public/assets/_zGPxyhe6.js.br +0 -0
  274. package/dist/public/assets/files-view-dOlOeiFQ.js +2 -0
  275. package/dist/public/assets/manage-workspace-CO4jGUSH.js +2 -0
  276. package/dist/public/assets/new-hhTJEH33.js +1 -0
  277. package/dist/public/assets/new-workspace-w6SV5jo2.js +2 -0
  278. package/dist/public/assets/review-view-DhbS4oNh.js +2 -0
  279. package/dist/public/assets/settings-hhTJEH33.js +1 -0
  280. package/dist/public/assets/settings-page-DpRlMZTu.js +2 -0
  281. package/dist/public/assets/styles-BfKP6xyD.css +1 -0
  282. package/dist/public/assets/styles-BfKP6xyD.css.br +0 -0
  283. package/dist/public/assets/w-hhTJEH33.js +1 -0
  284. package/dist/public/assets/w._-DSFmri_t.js +1 -0
  285. package/dist/public/assets/w._-hhTJEH33.js +1 -0
  286. package/dist/public/assets/w._-kFbho4FJ.js +1 -0
  287. package/dist/rtc-helper.mjs +1170 -0
  288. package/lib/preview/media.mjs +5 -4
  289. package/lib/setup/doctor.d.mts +11 -0
  290. package/lib/setup/doctor.mjs +23 -2
  291. package/lib/setup/firewall.d.mts +76 -0
  292. package/lib/setup/firewall.mjs +598 -0
  293. package/package.json +3 -2
  294. package/dist/_D1C-XIqT.js +0 -168
  295. package/dist/_HbAW5Cbt.js +0 -2
  296. package/dist/public/assets/_-5UNb9-C.js.br +0 -0
  297. package/dist/public/assets/_06rtP_SB.js +0 -1
  298. package/dist/public/assets/_14yhXek7.js +0 -1
  299. package/dist/public/assets/_14yhXek7.js.br +0 -0
  300. package/dist/public/assets/_1JXC9nFp.js.br +0 -0
  301. package/dist/public/assets/_1Yit_Y5R.js +0 -1
  302. package/dist/public/assets/_1ZPfR2B8.js +0 -6
  303. package/dist/public/assets/_1ZPfR2B8.js.br +0 -0
  304. package/dist/public/assets/_1xOByopP.js.br +0 -0
  305. package/dist/public/assets/_3aBl7SEn.js.br +0 -0
  306. package/dist/public/assets/_5aMLV2Vy.js +0 -1
  307. package/dist/public/assets/_7YrArFMB.js.br +0 -0
  308. package/dist/public/assets/_B1-W0Sn2.js.br +0 -0
  309. package/dist/public/assets/_B1D0PIz3.js.br +0 -0
  310. package/dist/public/assets/_B1agoKUa.js +0 -1
  311. package/dist/public/assets/_B3NfNm8M.js.br +0 -0
  312. package/dist/public/assets/_B7VYILLZ.js.br +0 -0
  313. package/dist/public/assets/_B7sPBy0J.js +0 -1
  314. package/dist/public/assets/_B81p6DSw.js +0 -1
  315. package/dist/public/assets/_BAkSKCkz.js +0 -1
  316. package/dist/public/assets/_BHZPTv1U.js +0 -1
  317. package/dist/public/assets/_BKHtiW96.js.br +0 -0
  318. package/dist/public/assets/_BKIx7IZP.js.br +0 -0
  319. package/dist/public/assets/_BLfzQY-3.js.br +0 -0
  320. package/dist/public/assets/_BPSTqkJn.js.br +0 -0
  321. package/dist/public/assets/_BQZOimBB.js +0 -1
  322. package/dist/public/assets/_BRQ6iQ5p.js.br +0 -0
  323. package/dist/public/assets/_BRVURc4u.js +0 -1
  324. package/dist/public/assets/_BSFJEuJX.js.br +0 -0
  325. package/dist/public/assets/_BSWN-v3P.js.br +0 -0
  326. package/dist/public/assets/_BTVAQr2c.js +0 -1
  327. package/dist/public/assets/_BUD1pBC0.js +0 -1
  328. package/dist/public/assets/_BVGzCczb.js +0 -1
  329. package/dist/public/assets/_BVQosHjY.js.br +0 -0
  330. package/dist/public/assets/_B_0C2_RO.js.br +0 -0
  331. package/dist/public/assets/_BaA31ms5.js +0 -1
  332. package/dist/public/assets/_BgCHJGlO.js +0 -1
  333. package/dist/public/assets/_BhpRLDbL.js +0 -1
  334. package/dist/public/assets/_BhpRLDbL.js.br +0 -0
  335. package/dist/public/assets/_BkKcjMAC.js +0 -1
  336. package/dist/public/assets/_BkcPY2j0.js.br +0 -0
  337. package/dist/public/assets/_BlTVd_3d.js +0 -1
  338. package/dist/public/assets/_BlqlLmEY.js +0 -1
  339. package/dist/public/assets/_BoNfxc9P.js +0 -1
  340. package/dist/public/assets/_BoPk1gXL.js +0 -1
  341. package/dist/public/assets/_BqswW9B9.js +0 -1
  342. package/dist/public/assets/_BricqzsO.js +0 -1
  343. package/dist/public/assets/_BrreckIa.js +0 -1
  344. package/dist/public/assets/_BsJZPHmz.js.br +0 -0
  345. package/dist/public/assets/_BtRYwaBb.js +0 -1
  346. package/dist/public/assets/_BvPBKjRP.js +0 -1
  347. package/dist/public/assets/_BvPBKjRP.js.br +0 -0
  348. package/dist/public/assets/_ByrrjMU9.js +0 -1
  349. package/dist/public/assets/_BzGHediB.js.br +0 -0
  350. package/dist/public/assets/_BzvTKm92.js +0 -1
  351. package/dist/public/assets/_C-EkQeP_.js.br +0 -0
  352. package/dist/public/assets/_C0Ib7MZF.js.br +0 -0
  353. package/dist/public/assets/_C2p1Z5Lf.js +0 -86
  354. package/dist/public/assets/_C2p1Z5Lf.js.br +0 -0
  355. package/dist/public/assets/_C3iiOlxd.js +0 -1
  356. package/dist/public/assets/_C81nSl09.js.br +0 -0
  357. package/dist/public/assets/_C8bqYk4J.js.br +0 -0
  358. package/dist/public/assets/_C98JZr09.js.br +0 -0
  359. package/dist/public/assets/_CB4VkmGZ.js +0 -1
  360. package/dist/public/assets/_CCVkYcN5.js.br +0 -0
  361. package/dist/public/assets/_CDY0RpYX.js.br +0 -0
  362. package/dist/public/assets/_CE2GEqMx.js +0 -1
  363. package/dist/public/assets/_CE2GEqMx.js.br +0 -0
  364. package/dist/public/assets/_CFUREq1M.js.br +0 -0
  365. package/dist/public/assets/_CHdh6bII.js +0 -1
  366. package/dist/public/assets/_CHdpLB95.js.br +0 -0
  367. package/dist/public/assets/_CKLGAGKJ.js.br +0 -0
  368. package/dist/public/assets/_CMRt2lyQ.js +0 -1
  369. package/dist/public/assets/_CN1zW1AT.js +0 -1
  370. package/dist/public/assets/_CN1zW1AT.js.br +0 -0
  371. package/dist/public/assets/_CUILmj9R.js +0 -1
  372. package/dist/public/assets/_CUhP8KQ8.js +0 -1
  373. package/dist/public/assets/_CUsrMpdL.js.br +0 -0
  374. package/dist/public/assets/_CVG0_w9T.js +0 -1
  375. package/dist/public/assets/_CWG813ur.js.br +0 -0
  376. package/dist/public/assets/_CY87Y7QL.js.br +0 -0
  377. package/dist/public/assets/_CYAsKva_.js +0 -1
  378. package/dist/public/assets/_CajKYNSY.js +0 -1
  379. package/dist/public/assets/_Cb_k_JA_.js.br +0 -0
  380. package/dist/public/assets/_Ce8PlfMD.js +0 -1
  381. package/dist/public/assets/_CfGEGt_k.js.br +0 -0
  382. package/dist/public/assets/_CfdK8EeQ.js.br +0 -0
  383. package/dist/public/assets/_CgOKmAwr.js +0 -1
  384. package/dist/public/assets/_CiYQhf81.js.br +0 -0
  385. package/dist/public/assets/_Cj9DHPRL.js +0 -1
  386. package/dist/public/assets/_ClHPjF-1.js.br +0 -0
  387. package/dist/public/assets/_Cmmdd2e3.js.br +0 -0
  388. package/dist/public/assets/_Cn9Ia4SS.js +0 -1
  389. package/dist/public/assets/_Cn9Ia4SS.js.br +0 -0
  390. package/dist/public/assets/_CnTzFbuG.js +0 -1
  391. package/dist/public/assets/_CqCenHOp.js.br +0 -0
  392. package/dist/public/assets/_CtqGSS84.js.br +0 -0
  393. package/dist/public/assets/_CttfwM-c.js +0 -1
  394. package/dist/public/assets/_CxA3Yab0.js.br +0 -0
  395. package/dist/public/assets/_Cx_aJfDp.js.br +0 -0
  396. package/dist/public/assets/_D2L_ILOW.js +0 -1
  397. package/dist/public/assets/_D47lCu0c.js +0 -1
  398. package/dist/public/assets/_D5W-uGIK.js +0 -1
  399. package/dist/public/assets/_D6BbZLrc.js +0 -1
  400. package/dist/public/assets/_D6RVeCAn.js +0 -1
  401. package/dist/public/assets/_D75V4jV-.js +0 -1
  402. package/dist/public/assets/_D9r_Fn0I.js.br +0 -0
  403. package/dist/public/assets/_DBD57l1T.js.br +0 -0
  404. package/dist/public/assets/_DCWILZgL.js +0 -1
  405. package/dist/public/assets/_DEOryr7w.js.br +0 -0
  406. package/dist/public/assets/_DF54Iz7o.js +0 -1
  407. package/dist/public/assets/_DFamU2Uj.js +0 -2
  408. package/dist/public/assets/_DFamU2Uj.js.br +0 -0
  409. package/dist/public/assets/_DHmBuyCO.js +0 -1
  410. package/dist/public/assets/_DHmBuyCO.js.br +0 -0
  411. package/dist/public/assets/_DJqEBkNd.js.br +0 -0
  412. package/dist/public/assets/_DL3w8c_p.js.br +0 -0
  413. package/dist/public/assets/_DLVL-4MP.js +0 -1
  414. package/dist/public/assets/_DM60dKRs.js.br +0 -0
  415. package/dist/public/assets/_DMJHTH74.js.br +0 -0
  416. package/dist/public/assets/_DMLv6KA6.js +0 -1
  417. package/dist/public/assets/_DQYpgyzr.js +0 -1
  418. package/dist/public/assets/_DQYpgyzr.js.br +0 -0
  419. package/dist/public/assets/_DQyjuXTi.js.br +0 -0
  420. package/dist/public/assets/_DRHCy0UF.js.br +0 -0
  421. package/dist/public/assets/_DSUBv-Bl.js.br +0 -0
  422. package/dist/public/assets/_DUFM9qLV.js +0 -1
  423. package/dist/public/assets/_DUFM9qLV.js.br +0 -0
  424. package/dist/public/assets/_DUs3ZGbj.js.br +0 -0
  425. package/dist/public/assets/_DX5u6Wvm.js.br +0 -0
  426. package/dist/public/assets/_DZvkgS3V.js.br +0 -0
  427. package/dist/public/assets/_Dbm6zAQS.js.br +0 -0
  428. package/dist/public/assets/_Ddf1gumD.js +0 -1
  429. package/dist/public/assets/_De6E9M7R.js +0 -1
  430. package/dist/public/assets/_DgWKSW-Y.js.br +0 -0
  431. package/dist/public/assets/_DhEN6T4v.js +0 -1
  432. package/dist/public/assets/_Di1ccq_2.js.br +0 -0
  433. package/dist/public/assets/_DifjKObE.js.br +0 -0
  434. package/dist/public/assets/_Dn10ABa-.js +0 -1
  435. package/dist/public/assets/_Do1ltY2a.js.br +0 -0
  436. package/dist/public/assets/_DomK7Scb.js.br +0 -0
  437. package/dist/public/assets/_DtWggCxt.js +0 -1
  438. package/dist/public/assets/_DtqqybyQ.js +0 -1
  439. package/dist/public/assets/_DvKnakqk.js.br +0 -0
  440. package/dist/public/assets/_DxD_AC3a.js.br +0 -0
  441. package/dist/public/assets/_Dxo7m9cG.js +0 -1
  442. package/dist/public/assets/_DzKW89Sx.js.br +0 -0
  443. package/dist/public/assets/_DzTHCioH.js +0 -1
  444. package/dist/public/assets/_EefWj858.js.br +0 -0
  445. package/dist/public/assets/_FlITqTCo.js.br +0 -0
  446. package/dist/public/assets/_GkEw2S0T.js +0 -1
  447. package/dist/public/assets/_HM_QqtJi.js.br +0 -0
  448. package/dist/public/assets/_HmkI4Rfn.js.br +0 -0
  449. package/dist/public/assets/_JEvx8ub4.js.br +0 -0
  450. package/dist/public/assets/_JOb8B3tz.js +0 -1
  451. package/dist/public/assets/_LEIhjFrO.js +0 -1
  452. package/dist/public/assets/_STJhwy7U.js +0 -1
  453. package/dist/public/assets/_TrcWRD8T.js.br +0 -0
  454. package/dist/public/assets/_UaKPY71M.js.br +0 -0
  455. package/dist/public/assets/_UhK3lkm0.js +0 -1
  456. package/dist/public/assets/_Zeftz3t1.js +0 -1
  457. package/dist/public/assets/__cyRVmTO.js.br +0 -0
  458. package/dist/public/assets/_awfYTHit.js +0 -1
  459. package/dist/public/assets/_dsj7vMTW.js.br +0 -0
  460. package/dist/public/assets/_eqpiOhSM.js +0 -1
  461. package/dist/public/assets/_iutZ4EDe.js.br +0 -0
  462. package/dist/public/assets/_kWQ0dtYZ.js.br +0 -0
  463. package/dist/public/assets/_knvMqXQh.js +0 -1
  464. package/dist/public/assets/_lwC7sIgC.js +0 -1
  465. package/dist/public/assets/_m2tRa0jk.js.br +0 -0
  466. package/dist/public/assets/_pXG8fRON.js.br +0 -0
  467. package/dist/public/assets/_r541ENtr.js +0 -1
  468. package/dist/public/assets/_rrUx1wn3.js +0 -1
  469. package/dist/public/assets/_tJvcu7sD.js +0 -1
  470. package/dist/public/assets/_vcLBrU2m.js.br +0 -0
  471. package/dist/public/assets/_xZC4Vm70.js +0 -1
  472. package/dist/public/assets/_yAr5r-uL.js +0 -1
  473. package/dist/public/assets/_yUmRJIJ6.js.br +0 -0
  474. package/dist/public/assets/files-view-CTadakLK.js +0 -2
  475. package/dist/public/assets/manage-workspace-HUAS6qjy.js +0 -2
  476. package/dist/public/assets/new-CirJkJFl.js +0 -1
  477. package/dist/public/assets/new-workspace-C3t1nA6z.js +0 -2
  478. package/dist/public/assets/review-view-DKPNtgmc.js +0 -2
  479. package/dist/public/assets/settings-CirJkJFl.js +0 -1
  480. package/dist/public/assets/settings-page-DH-9cN-8.js +0 -2
  481. package/dist/public/assets/styles-CIeNhVxC.css +0 -1
  482. package/dist/public/assets/styles-CIeNhVxC.css.br +0 -0
  483. package/dist/public/assets/w-CirJkJFl.js +0 -1
  484. package/dist/public/assets/w._-BnShhzdq.js +0 -1
  485. package/dist/public/assets/w._-CirJkJFl.js +0 -1
  486. package/dist/public/assets/w._-aCTYfruH.js +0 -1
@@ -0,0 +1,1170 @@
1
+ import { syncBuiltinESMExports } from "node:module";
2
+ import childProcess from "node:child_process";
3
+ import net from "node:net";
4
+ import zlib from "node:zlib";
5
+ import dgram from "node:dgram";
6
+ import fs from "node:fs";
7
+ //#region src/server/rtc/upnp.ts
8
+ /**
9
+ * A UPnP IGD client, just enough to open one UDP port on the LAN's router
10
+ * for direct connections (server/rtc): find the gateway (an SSDP
11
+ * M-SEARCH), read its description for the WAN connection service, then
12
+ * SOAP `GetExternalIPAddress`, `AddPortMapping` and `DeletePortMapping`. A
13
+ * phone behind carrier NAT cannot punch through to this machine by itself;
14
+ * with the router forwarding the port, its checks arrive as they would on
15
+ * the LAN.
16
+ *
17
+ * Only on demand: the user allowed it (Settings), and a page that could
18
+ * use it failed to connect without it (server/rtc). The mapping is leased
19
+ * (LEASE_S) and renewed while a peer uses it, given back once none has for
20
+ * IDLE_MS, and deleted when the permission goes or the helper this runs in
21
+ * exits (helper.ts); the lease is what closes it if the helper dies
22
+ * without deleting it.
23
+ *
24
+ * Whatever answers on the LAN is untrusted: only a default gateway is
25
+ * listened to, only its own address is fetched from and posted to, and
26
+ * nothing it sends is read past a small size.
27
+ */
28
+ /** A fixed SSDP target (`WORK_UPNP_SSDP`, host:port) instead of the group
29
+ * and the default gateways: a fake gateway to test against, which is then
30
+ * the one gateway trusted. */
31
+ var OVERRIDE = hostPort(process.env.WORK_UPNP_SSDP);
32
+ var GROUP = "239.255.255.250";
33
+ var SSDP_PORT = OVERRIDE?.port ?? 1900;
34
+ var SEARCH = [
35
+ "urn:schemas-upnp-org:device:InternetGatewayDevice:1",
36
+ "urn:schemas-upnp-org:device:InternetGatewayDevice:2",
37
+ "urn:schemas-upnp-org:service:WANIPConnection:1",
38
+ "urn:schemas-upnp-org:service:WANIPConnection:2",
39
+ "urn:schemas-upnp-org:service:WANPPPConnection:1"
40
+ ];
41
+ /** The services that map ports, best first. */
42
+ var SERVICES = [
43
+ "urn:schemas-upnp-org:service:WANIPConnection:2",
44
+ "urn:schemas-upnp-org:service:WANIPConnection:1",
45
+ "urn:schemas-upnp-org:service:WANPPPConnection:1"
46
+ ];
47
+ /** An hour: long enough that renewing costs nothing, short enough that a
48
+ * machine that vanished without deleting it does not leave the port open
49
+ * for good. `WORK_UPNP_LEASE` (seconds) shortens it, and the idle release
50
+ * with it, to watch them in a test. */
51
+ var LEASE_S = Number(process.env.WORK_UPNP_LEASE) || 3600;
52
+ /** Unused this long, the mapping is given back: a page that walks away
53
+ * does not leave the router's port open. */
54
+ var IDLE_MS = Math.min(12e4, LEASE_S * 1e3 / 4);
55
+ var CHECK_MS = Math.max(500, IDLE_MS / 4);
56
+ /** Every network step's limit: a LAN gateway answers in milliseconds. */
57
+ var TIMEOUT_MS = 3e3;
58
+ /** The most read of what a device on the LAN sends. */
59
+ var MAX_SSDP = 2048;
60
+ var MAX_XML = 65536;
61
+ var MAX_SOAP = 16384;
62
+ /** How long a found gateway is used before looking again. */
63
+ var GATEWAY_TTL_MS = 6e5;
64
+ var DESCRIPTION = "work direct connections";
65
+ var UpnpError = class extends Error {
66
+ code;
67
+ constructor(code, message) {
68
+ super(message);
69
+ this.code = code;
70
+ }
71
+ };
72
+ var status = { state: "off" };
73
+ var mapping = null;
74
+ var gateway = null;
75
+ var busy = null;
76
+ var renewTimer;
77
+ var useTimer;
78
+ var lastUse = 0;
79
+ var inUse = () => false;
80
+ var listeners = /* @__PURE__ */ new Set();
81
+ function upnpStatus() {
82
+ return status;
83
+ }
84
+ function onUpnpStatus(fn) {
85
+ listeners.add(fn);
86
+ return () => listeners.delete(fn);
87
+ }
88
+ function setStatus(next) {
89
+ status = next;
90
+ for (const fn of listeners) fn(next);
91
+ }
92
+ /** The router's address and the port it forwards to this machine's
93
+ * `port`, when mapped. */
94
+ function upnpCandidate() {
95
+ return mapping && status.state === "mapped" ? {
96
+ ip: mapping.externalIp,
97
+ port: mapping.externalPort
98
+ } : null;
99
+ }
100
+ /** Map UDP `port` on the router, or keep the mapping there is. `used`
101
+ * says, as it is checked, whether a peer still uses it. Settles when the
102
+ * mapping is made or has failed; the status says which. */
103
+ function upnpWant(port, used) {
104
+ lastUse = Date.now();
105
+ inUse = used;
106
+ if (busy) return busy;
107
+ if (mapping && mapping.internalPort === port && status.state === "mapped") return Promise.resolve();
108
+ busy = (async () => {
109
+ if (mapping) await unmap();
110
+ await map(port);
111
+ })().finally(() => {
112
+ busy = null;
113
+ });
114
+ return busy;
115
+ }
116
+ /** Delete the mapping, if there is one (the permission went, it is no
117
+ * longer used, or the server is stopping). */
118
+ async function upnpRelease() {
119
+ clearTimeout(renewTimer);
120
+ clearInterval(useTimer);
121
+ useTimer = void 0;
122
+ inUse = () => false;
123
+ await busy?.catch(() => {});
124
+ await unmap();
125
+ if (status.state !== "off") setStatus({ state: "off" });
126
+ }
127
+ async function map(port) {
128
+ setStatus({ state: "mapping" });
129
+ try {
130
+ const gw = await findGateway();
131
+ const lanIp = await localAddressFor(gw.host);
132
+ const externalIp = tag(await soap(gw, "GetExternalIPAddress", []), "NewExternalIPAddress");
133
+ if (!net.isIPv4(externalIp)) throw new Error(`the router gave no WAN address (${externalIp || "none"})`);
134
+ const ports = [port, ...[
135
+ 1,
136
+ 2,
137
+ 3
138
+ ].map(() => 49152 + randomInt(16e3))];
139
+ let lease = LEASE_S;
140
+ let made = null;
141
+ let last;
142
+ for (const external of ports) try {
143
+ await addMapping(gw, external, port, lanIp, lease);
144
+ made = external;
145
+ break;
146
+ } catch (err) {
147
+ last = err;
148
+ if (err instanceof UpnpError && err.code === 725 && lease) {
149
+ lease = 0;
150
+ try {
151
+ await addMapping(gw, external, port, lanIp, lease);
152
+ made = external;
153
+ break;
154
+ } catch (again) {
155
+ last = again;
156
+ }
157
+ }
158
+ if (!(last instanceof UpnpError) || ![718, 729].includes(last.code)) break;
159
+ }
160
+ if (made === null) throw last;
161
+ mapping = {
162
+ gateway: gw,
163
+ externalIp,
164
+ externalPort: made,
165
+ internalPort: port,
166
+ lanIp,
167
+ lease
168
+ };
169
+ setStatus({
170
+ state: "mapped",
171
+ external: `${externalIp}:${made}`,
172
+ internal: `${lanIp}:${port}`,
173
+ expires: lease ? Date.now() + lease * 1e3 : 0,
174
+ note: privateV4(externalIp) ? `the router's own WAN address ${externalIp} is private: another NAT is in front of it, so this opens nothing to the internet` : void 0
175
+ });
176
+ scheduleRenew();
177
+ watchUse();
178
+ } catch (err) {
179
+ gateway = null;
180
+ setStatus({
181
+ state: "error",
182
+ error: describe(err)
183
+ });
184
+ }
185
+ }
186
+ /** Given back once no peer has used it for IDLE_MS. */
187
+ function watchUse() {
188
+ clearInterval(useTimer);
189
+ useTimer = setInterval(() => {
190
+ if (inUse()) lastUse = Date.now();
191
+ else if (Date.now() - lastUse > IDLE_MS) upnpRelease();
192
+ }, CHECK_MS);
193
+ useTimer.unref?.();
194
+ }
195
+ function scheduleRenew() {
196
+ clearTimeout(renewTimer);
197
+ if (!mapping?.lease) return;
198
+ renewTimer = setTimeout(renew, LEASE_S * 1e3 / 2);
199
+ renewTimer.unref?.();
200
+ }
201
+ async function renew() {
202
+ const m = mapping;
203
+ if (!m) return;
204
+ try {
205
+ await addMapping(m.gateway, m.externalPort, m.internalPort, m.lanIp, m.lease);
206
+ setStatus({
207
+ ...status,
208
+ expires: Date.now() + m.lease * 1e3
209
+ });
210
+ scheduleRenew();
211
+ } catch (err) {
212
+ mapping = null;
213
+ gateway = null;
214
+ setStatus({
215
+ state: "error",
216
+ error: `renewing: ${describe(err)}`
217
+ });
218
+ upnpWant(m.internalPort, inUse);
219
+ }
220
+ }
221
+ async function unmap() {
222
+ const m = mapping;
223
+ mapping = null;
224
+ if (!m) return;
225
+ await soap(m.gateway, "DeletePortMapping", [
226
+ ["NewRemoteHost", ""],
227
+ ["NewExternalPort", m.externalPort],
228
+ ["NewProtocol", "UDP"]
229
+ ]).catch(() => {});
230
+ }
231
+ function addMapping(gw, external, internal, lanIp, lease) {
232
+ return soap(gw, "AddPortMapping", [
233
+ ["NewRemoteHost", ""],
234
+ ["NewExternalPort", external],
235
+ ["NewProtocol", "UDP"],
236
+ ["NewInternalPort", internal],
237
+ ["NewInternalClient", lanIp],
238
+ ["NewEnabled", 1],
239
+ ["NewPortMappingDescription", DESCRIPTION],
240
+ ["NewLeaseDuration", lease]
241
+ ]);
242
+ }
243
+ async function soap(gw, action, args) {
244
+ const fields = args.map(([name, value]) => `<${name}>${escapeXml(String(value))}</${name}>`).join("");
245
+ const body = `<?xml version="1.0"?>\r
246
+ <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:${action} xmlns:u="${gw.service}">${fields}</u:${action}></s:Body></s:Envelope>`;
247
+ const res = await fetch(gw.control, {
248
+ method: "POST",
249
+ headers: {
250
+ "content-type": "text/xml; charset=\"utf-8\"",
251
+ soapaction: `"${gw.service}#${action}"`
252
+ },
253
+ body,
254
+ redirect: "error",
255
+ signal: AbortSignal.timeout(TIMEOUT_MS)
256
+ });
257
+ const text = await readCapped(res, MAX_SOAP);
258
+ if (!res.ok) {
259
+ const code = Number(tag(text, "errorCode")) || 0;
260
+ const what = tag(text, "errorDescription") || `HTTP ${res.status}`;
261
+ throw new UpnpError(code, `${action}: ${code ? `${code} ` : ""}${what}`);
262
+ }
263
+ return text;
264
+ }
265
+ async function findGateway() {
266
+ if (gateway && Date.now() - gateway.found < GATEWAY_TTL_MS) return gateway;
267
+ const gateways = OVERRIDE ? [OVERRIDE.host] : await defaultGateways();
268
+ if (!gateways.length) throw new Error("this machine has no default gateway");
269
+ const answers = await search(gateways);
270
+ let last = /* @__PURE__ */ new Error("no UPnP gateway answered on the LAN");
271
+ for (const { location, from } of answers) try {
272
+ gateway = await describeGateway(location, from);
273
+ return gateway;
274
+ } catch (err) {
275
+ last = err;
276
+ }
277
+ throw last;
278
+ }
279
+ /** The device descriptions the gateways point at, from answers to a search
280
+ * sent to the SSDP group and to each gateway directly — a host firewall
281
+ * (firewalld's default zones) drops a gateway's unicast answer to a
282
+ * multicast search, matching no flow this machine opened, where the answer
283
+ * to a query sent to it comes back on one. Only an answer from a gateway's
284
+ * own address, naming that address, is kept: anything else on the LAN can
285
+ * answer a search. The first answer ends the wait a moment later. */
286
+ function search(gateways) {
287
+ return new Promise((resolve) => {
288
+ const found = /* @__PURE__ */ new Map();
289
+ const sock = dgram.createSocket({
290
+ type: "udp4",
291
+ reuseAddr: true
292
+ });
293
+ let timer;
294
+ const finish = () => {
295
+ clearTimeout(timer);
296
+ try {
297
+ sock.close();
298
+ } catch {}
299
+ resolve([...found].map(([location, from]) => ({
300
+ location,
301
+ from
302
+ })));
303
+ };
304
+ timer = setTimeout(finish, TIMEOUT_MS);
305
+ sock.on("error", finish);
306
+ sock.on("message", (msg, rinfo) => {
307
+ if (msg.length > MAX_SSDP || !gateways.includes(rinfo.address)) return;
308
+ const location = /^location:\s*(\S+)/im.exec(msg.toString())?.[1];
309
+ if (!location || !trustedUrl(location, rinfo.address)) return;
310
+ if (!found.size) {
311
+ clearTimeout(timer);
312
+ timer = setTimeout(finish, 300);
313
+ }
314
+ found.set(location, rinfo.address);
315
+ });
316
+ sock.bind(0, () => {
317
+ const targets = OVERRIDE ? [OVERRIDE.host] : [GROUP, ...gateways];
318
+ try {
319
+ if (!OVERRIDE) sock.setMulticastTTL(2);
320
+ } catch {}
321
+ for (const host of targets) for (const st of SEARCH) {
322
+ const req = `M-SEARCH * HTTP/1.1\r\nHOST: ${host}:${SSDP_PORT}\r\nMAN: "ssdp:discover"\r\nMX: 2\r\nST: ${st}\r\n\r\n`;
323
+ sock.send(req, SSDP_PORT, host);
324
+ }
325
+ });
326
+ });
327
+ }
328
+ /** Where the default gateways come from when /proc/net/route does not
329
+ * say (macOS, whose `route` has to be run): this module runs in direct
330
+ * connections' helper, which never starts a process (helper.ts), so the
331
+ * server runs it and answers. */
332
+ var gatewayLookup = async () => [];
333
+ function setGatewayLookup(fn) {
334
+ gatewayLookup = fn;
335
+ }
336
+ /** The IPv4 default gateways on the LAN: Linux's routing table, else what
337
+ * the server's `route` says. */
338
+ async function defaultGateways() {
339
+ const lan = (ips) => ips.filter((ip) => privateV4(ip));
340
+ try {
341
+ return lan(fs.readFileSync("/proc/net/route", "utf8").split("\n").slice(1).map((line) => line.trim().split(/\s+/)).filter((f) => f[1] === "00000000" && /^[0-9A-F]{8}$/i.test(f[2] ?? "")).map((f) => [
342
+ 3,
343
+ 2,
344
+ 1,
345
+ 0
346
+ ].map((i) => parseInt(f[2].slice(i * 2, i * 2 + 2), 16)).join(".")));
347
+ } catch {}
348
+ return lan((await gatewayLookup().catch(() => [])).filter(net.isIPv4));
349
+ }
350
+ /** Whether `url` is one to fetch from the gateway at `host`: http(s), at
351
+ * that very address, which is a LAN one. */
352
+ function trustedUrl(url, host) {
353
+ let parsed;
354
+ try {
355
+ parsed = new URL(url);
356
+ } catch {
357
+ return false;
358
+ }
359
+ return (parsed.protocol === "http:" || parsed.protocol === "https:") && parsed.hostname === host && lanAddress(host);
360
+ }
361
+ function lanAddress(host) {
362
+ if (!net.isIPv4(host)) return false;
363
+ if (OVERRIDE && host === OVERRIDE.host && /^127\./.test(host)) return true;
364
+ return privateV4(host);
365
+ }
366
+ /** The gateway's WAN connection service, from its description at
367
+ * `location`. Its control URL is taken relative to that address and must
368
+ * stay on it: a description's `URLBase` or an absolute control URL could
369
+ * send the requests anywhere. */
370
+ async function describeGateway(location, host) {
371
+ const res = await fetch(location, {
372
+ redirect: "error",
373
+ signal: AbortSignal.timeout(TIMEOUT_MS)
374
+ });
375
+ const xml = await readCapped(res, MAX_XML);
376
+ if (!res.ok) throw new Error(`${location}: HTTP ${res.status}`);
377
+ const services = [...xml.matchAll(/<service>([\s\S]*?)<\/service>/gi)].map((m) => ({
378
+ type: tag(m[1], "serviceType"),
379
+ control: tag(m[1], "controlURL")
380
+ }));
381
+ for (const type of SERVICES) {
382
+ const s = services.find((x) => x.type === type && x.control);
383
+ if (!s) continue;
384
+ let control;
385
+ try {
386
+ control = new URL(s.control, location).toString();
387
+ } catch {
388
+ continue;
389
+ }
390
+ if (!trustedUrl(control, host)) throw new Error(`${host}'s description points its control elsewhere (${control})`);
391
+ return {
392
+ control,
393
+ service: type,
394
+ host,
395
+ found: Date.now()
396
+ };
397
+ }
398
+ throw new Error(`${location} offers no WAN connection service`);
399
+ }
400
+ /** A response's text, refused past `max` bytes. */
401
+ async function readCapped(res, max) {
402
+ const reader = res.body?.getReader();
403
+ if (!reader) return "";
404
+ const chunks = [];
405
+ let size = 0;
406
+ for (;;) {
407
+ const { done, value } = await reader.read();
408
+ if (done) break;
409
+ size += value.length;
410
+ if (size > max) {
411
+ await reader.cancel().catch(() => {});
412
+ throw new Error(`the gateway's answer is over ${max} bytes`);
413
+ }
414
+ chunks.push(value);
415
+ }
416
+ return Buffer.concat(chunks).toString("utf8");
417
+ }
418
+ /** This machine's address on the way to `host`: the one the router
419
+ * forwards to. A connected UDP socket sends nothing. */
420
+ function localAddressFor(host) {
421
+ return new Promise((resolve, reject) => {
422
+ const sock = dgram.createSocket("udp4");
423
+ sock.on("error", (err) => {
424
+ sock.close();
425
+ reject(err);
426
+ });
427
+ sock.connect(9, host, () => {
428
+ try {
429
+ resolve(sock.address().address);
430
+ } catch (err) {
431
+ reject(err);
432
+ } finally {
433
+ sock.close();
434
+ }
435
+ });
436
+ });
437
+ }
438
+ function tag(xml, name) {
439
+ const m = new RegExp(`<(?:\\w+:)?${name}>([^<]*)</(?:\\w+:)?${name}>`, "i").exec(xml);
440
+ return m ? unescapeXml(m[1].trim()) : "";
441
+ }
442
+ function escapeXml(s) {
443
+ return s.replace(/[<>&'"]/g, (c) => `&#${c.charCodeAt(0)};`);
444
+ }
445
+ function unescapeXml(s) {
446
+ return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, "\"").replace(/&apos;/g, "'").replace(/&amp;/g, "&");
447
+ }
448
+ var PRIVATE = new net.BlockList();
449
+ for (const [ip, bits] of [
450
+ ["10.0.0.0", 8],
451
+ ["172.16.0.0", 12],
452
+ ["192.168.0.0", 16],
453
+ ["100.64.0.0", 10],
454
+ ["169.254.0.0", 16]
455
+ ]) PRIVATE.addSubnet(ip, bits, "ipv4");
456
+ function privateV4(ip) {
457
+ return net.isIPv4(ip) && PRIVATE.check(ip, "ipv4");
458
+ }
459
+ function hostPort(value) {
460
+ const m = /^\[?([^\]]+?)\]?:(\d+)$/.exec(value?.trim() || "");
461
+ return m ? {
462
+ host: m[1],
463
+ port: Number(m[2])
464
+ } : null;
465
+ }
466
+ function randomInt(n) {
467
+ return Math.floor(Math.random() * n);
468
+ }
469
+ function describe(err) {
470
+ if (err instanceof Error) {
471
+ if (err.name === "TimeoutError") return "the router did not answer";
472
+ return err.message;
473
+ }
474
+ return String(err);
475
+ }
476
+ var HEAD = 9;
477
+ /** Nothing either end sends comes near this: a larger length is a pipe
478
+ * out of step, and reading on would misread everything after it. */
479
+ var MAX_FRAME = 67108864;
480
+ /** One end of the pipe. */
481
+ var Wire = class {
482
+ sock;
483
+ onFrame;
484
+ pending = null;
485
+ corked = false;
486
+ broken = false;
487
+ constructor(sock, onFrame) {
488
+ this.sock = sock;
489
+ this.onFrame = onFrame;
490
+ sock.on("data", (data) => this.read(data));
491
+ }
492
+ send(kind, chan, payload) {
493
+ if (this.broken || this.sock.destroyed) return;
494
+ const head = Buffer.allocUnsafe(HEAD);
495
+ head.writeUInt32LE(payload.length + HEAD - 4, 0);
496
+ head.writeUInt8(kind, 4);
497
+ head.writeUInt32LE(chan, 5);
498
+ if (!this.corked) {
499
+ this.corked = true;
500
+ this.sock.cork();
501
+ process.nextTick(() => {
502
+ this.corked = false;
503
+ this.sock.uncork();
504
+ });
505
+ }
506
+ this.sock.write(head);
507
+ this.sock.write(payload);
508
+ }
509
+ message(msg) {
510
+ this.send(0, 0, Buffer.from(JSON.stringify(msg)));
511
+ }
512
+ read(data) {
513
+ let buf = this.pending ? Buffer.concat([this.pending, data]) : data;
514
+ while (buf.length >= 4) {
515
+ const size = buf.readUInt32LE(0);
516
+ if (size < 5 || size > MAX_FRAME) {
517
+ this.broken = true;
518
+ this.sock.destroy(/* @__PURE__ */ new Error(`a frame of ${size} bytes`));
519
+ return;
520
+ }
521
+ if (buf.length < size + 4) break;
522
+ const kind = buf.readUInt8(4);
523
+ const chan = buf.readUInt32LE(5);
524
+ const payload = buf.subarray(HEAD, size + 4);
525
+ buf = buf.subarray(size + 4);
526
+ this.onFrame(kind, chan, payload);
527
+ }
528
+ this.pending = buf.length ? buf : null;
529
+ }
530
+ };
531
+ /** `host 192.168.0.5:7171`: a candidate as the log and the page show it. */
532
+ function side(c) {
533
+ const host = c.address?.includes(":") ? `[${c.address}]` : c.address;
534
+ return `${c.type ?? "?"} ${host}:${c.port}`;
535
+ }
536
+ //#endregion
537
+ //#region src/server/rtc/helper.ts
538
+ /**
539
+ * Direct connections' network half, a process of its own that the server
540
+ * starts at a page's first offer (link.ts): node-datachannel, the UDP
541
+ * port, every PeerConnection and DataChannel, the compression of what
542
+ * terminals send down them, and the router port (upnp.ts). The server
543
+ * keeps the rest — the signalling on its events sockets and the access
544
+ * and Origin checks they passed, the path verdict, the firewall, and the
545
+ * terminals themselves — and talks to this over a pipe (wire.ts): each
546
+ * terminal's DataChannel is a channel number there, which the server hands
547
+ * term-ws as a TermChannel.
548
+ *
549
+ * It never starts a process, and that is why it is one. libjuice opens the
550
+ * ICE socket without close-on-exec, so whatever is started from the
551
+ * process holding it inherits the port: a tmux server started from the
552
+ * server kept it bound across the server's restart, and the next bind
553
+ * failed (EADDRINUSE) for as long as tmux ran. Here nothing inherits it,
554
+ * and the port is free the moment this exits — so it can exit when idle.
555
+ * The spawning functions are replaced below so a change that starts one
556
+ * fails at once rather than leaking the socket; the gateway that macOS
557
+ * only names through `route`, the server looks up (`gateways?`).
558
+ *
559
+ * It exits when the pipe closes (the server stopped or was killed), on
560
+ * SIGTERM or SIGINT, when the server says so, and after IDLE_MS with no
561
+ * peer — each time giving back a router port it holds first. A native
562
+ * crash here costs the pages their peers, not the server: the server
563
+ * sees the pipe close, tells each page, and their terminals resume on
564
+ * their websockets.
565
+ */
566
+ for (const name of [
567
+ "spawn",
568
+ "spawnSync",
569
+ "exec",
570
+ "execSync",
571
+ "execFile",
572
+ "execFileSync",
573
+ "fork"
574
+ ]) childProcess[name] = () => {
575
+ throw new Error("direct connections' helper never starts a process: it would inherit the UDP port");
576
+ };
577
+ syncBuiltinESMExports();
578
+ /** A page pings every few seconds while it is open: one silent this long
579
+ * is gone (a phone put away, a laptop shut), and its terminals' attaches
580
+ * are held for it as a lost socket's are. */
581
+ var SILENT_MS = 6e4;
582
+ /** A peer that has not connected in this long never will. */
583
+ var CONNECT_MS = 3e4;
584
+ /** What this end accepts in one message, and so what a page may send. */
585
+ var MAX_MESSAGE = 262144;
586
+ /** Output is sent in pieces of at most this many characters (48 KiB of
587
+ * UTF-8 at worst), well under what any browser accepts in one message; a
588
+ * compressor's chunks are no larger. */
589
+ var PIECE = 16384;
590
+ /** How often a channel whose output is still queued says how much. */
591
+ var REPORT_MS = 50;
592
+ var pipe = new net.Socket({
593
+ fd: 3,
594
+ readable: true,
595
+ writable: true
596
+ });
597
+ var wire = new Wire(pipe, frame);
598
+ var peers = /* @__PURE__ */ new Map();
599
+ var chans = /* @__PURE__ */ new Map();
600
+ /** A page's candidates that came before its peer was made. */
601
+ var early = /* @__PURE__ */ new Map();
602
+ var ndc = null;
603
+ var port = 0;
604
+ var stun = [];
605
+ var idleMs = 6e5;
606
+ var chanCount = 0;
607
+ /** When it last had a peer, or an offer. */
608
+ var busyAt = Date.now();
609
+ var leaving = false;
610
+ /** The port's mux, held for this process's life: a peer of no page's,
611
+ * gathered on the port and never answered, whose ICE agent keeps the mux
612
+ * registered. Left to itself the mux closes with its last peer, and a
613
+ * peer made while it closes cannot bind the port. */
614
+ var anchor = null;
615
+ function say(msg) {
616
+ wire.message(msg);
617
+ }
618
+ pipe.on("error", () => {});
619
+ pipe.on("close", () => void leave("the server went"));
620
+ for (const signal of ["SIGTERM", "SIGINT"]) process.on(signal, () => void leave(signal));
621
+ function frame(kind, chan, payload) {
622
+ if (kind !== 0) return chans.get(chan)?.output(kind, payload);
623
+ let msg;
624
+ try {
625
+ msg = JSON.parse(payload.toString());
626
+ } catch {
627
+ return;
628
+ }
629
+ switch (msg.t) {
630
+ case "init":
631
+ start(msg);
632
+ return;
633
+ case "offer":
634
+ offer(msg);
635
+ return;
636
+ case "candidate": {
637
+ const peer = peers.get(msg.id);
638
+ if (!peer) {
639
+ const list = early.get(msg.id);
640
+ if (list && list.length < 64) list.push(msg);
641
+ return;
642
+ }
643
+ try {
644
+ peer.pc.addRemoteCandidate(msg.candidate, msg.mid);
645
+ } catch {}
646
+ return;
647
+ }
648
+ case "close":
649
+ closePeer(msg.id, null);
650
+ return;
651
+ case "end":
652
+ chans.get(msg.chan)?.close(msg.code);
653
+ return;
654
+ case "drop":
655
+ chans.get(msg.chan)?.terminate();
656
+ return;
657
+ case "upnp.release":
658
+ upnpRelease();
659
+ return;
660
+ case "gateways":
661
+ lookups.get(msg.n)?.(msg.ips);
662
+ return;
663
+ case "shutdown":
664
+ leave("the server is stopping");
665
+ return;
666
+ }
667
+ }
668
+ var lookups = /* @__PURE__ */ new Map();
669
+ var lookupCount = 0;
670
+ setGatewayLookup(() => new Promise((resolve) => {
671
+ const n = ++lookupCount;
672
+ const done = (ips) => {
673
+ lookups.delete(n);
674
+ resolve(ips);
675
+ };
676
+ lookups.set(n, done);
677
+ say({
678
+ t: "gateways?",
679
+ n
680
+ });
681
+ setTimeout(() => done([]), 3e3).unref();
682
+ }));
683
+ onUpnpStatus((status) => say({
684
+ t: "upnp",
685
+ status
686
+ }));
687
+ /** node-datachannel and the port, then ready; either failing is said, and
688
+ * this exits (the server starts another at a later offer). */
689
+ async function start(init) {
690
+ port = init.port;
691
+ stun = init.stun;
692
+ idleMs = init.idleMs;
693
+ try {
694
+ ndc = await import("node-datachannel");
695
+ } catch (err) {
696
+ say({
697
+ t: "failed",
698
+ error: message(err),
699
+ unavailable: true
700
+ });
701
+ return leave("node-datachannel did not load");
702
+ }
703
+ if (init.log) ndc.initLogger("Debug", (level, text) => console.error(`[rtc ${level}] ${text}`));
704
+ ndc.setSctpSettings({
705
+ sendBufferSize: 262144,
706
+ recvBufferSize: 262144
707
+ });
708
+ try {
709
+ anchor = await hold(ndc);
710
+ } catch (err) {
711
+ say({
712
+ t: "failed",
713
+ error: message(err),
714
+ bind: true
715
+ });
716
+ return leave("the port could not be bound");
717
+ }
718
+ say({ t: "ready" });
719
+ setInterval(() => {
720
+ if (!peers.size && Date.now() - busyAt > idleMs) leave("idle");
721
+ }, Math.min(6e4, Math.max(1e3, idleMs / 4))).unref();
722
+ }
723
+ function hold(lib) {
724
+ return new Promise((resolve, reject) => {
725
+ const pc = new lib.PeerConnection("work-port", {
726
+ iceServers: [],
727
+ enableIceUdpMux: true,
728
+ portRangeBegin: port,
729
+ portRangeEnd: port
730
+ });
731
+ let settled = false;
732
+ const done = () => {
733
+ if (settled) return;
734
+ settled = true;
735
+ resolve(pc);
736
+ };
737
+ pc.onGatheringStateChange((state) => {
738
+ if (state === "complete") done();
739
+ });
740
+ setTimeout(done, 1e3);
741
+ try {
742
+ pc.createDataChannel("hold");
743
+ } catch (err) {
744
+ settled = true;
745
+ try {
746
+ pc.close();
747
+ } catch {}
748
+ reject(/* @__PURE__ */ new Error(`UDP port ${port} could not be bound (${message(err)})`));
749
+ }
750
+ });
751
+ }
752
+ async function offer(msg) {
753
+ const { id, sdp } = msg;
754
+ if (!ndc || leaving) return;
755
+ busyAt = Date.now();
756
+ early.set(id, early.get(id) ?? []);
757
+ if (msg.usesPort) await Promise.race([upnpWant(port, portInUse), new Promise((r) => setTimeout(r, 2500))]);
758
+ if (msg.needsPort) say({
759
+ t: "port",
760
+ id
761
+ });
762
+ const queued = early.get(id);
763
+ early.delete(id);
764
+ if (!queued) return;
765
+ closePeer(id, null);
766
+ let pc;
767
+ try {
768
+ pc = new ndc.PeerConnection(`work-${id}`, {
769
+ iceServers: stun,
770
+ enableIceUdpMux: true,
771
+ portRangeBegin: port,
772
+ portRangeEnd: port,
773
+ maxMessageSize: MAX_MESSAGE
774
+ });
775
+ } catch (err) {
776
+ say({
777
+ t: "closed",
778
+ id,
779
+ why: message(err)
780
+ });
781
+ return;
782
+ }
783
+ const now = Date.now();
784
+ const peer = {
785
+ id,
786
+ pc,
787
+ state: "new",
788
+ channels: /* @__PURE__ */ new Set(),
789
+ heard: now,
790
+ born: now,
791
+ usesPort: msg.usesPort,
792
+ timer: setInterval(() => {
793
+ if (peer.state !== "connected" && Date.now() - peer.born > CONNECT_MS) closePeer(id, "never connected");
794
+ else if (Date.now() - peer.heard > SILENT_MS) closePeer(id, "silent: its page stopped pinging");
795
+ }, 5e3)
796
+ };
797
+ peers.set(id, peer);
798
+ pc.onLocalDescription((answer, type) => {
799
+ say({
800
+ t: "answer",
801
+ id,
802
+ sdp: answer,
803
+ type
804
+ });
805
+ const ext = msg.routerCandidate ? upnpCandidate() : null;
806
+ if (ext) say({
807
+ t: "candidate",
808
+ id,
809
+ candidate: `candidate:upnp1 1 UDP 1694498815 ${ext.ip} ${ext.port} typ srflx raddr 0.0.0.0 rport 0`,
810
+ mid: /^a=mid:(\S+)/m.exec(sdp)?.[1] ?? "0"
811
+ });
812
+ });
813
+ pc.onLocalCandidate((candidate, mid) => say({
814
+ t: "candidate",
815
+ id,
816
+ candidate: candidate.replace(/^a=/, ""),
817
+ mid
818
+ }));
819
+ pc.onStateChange((state) => {
820
+ peer.state = state;
821
+ if (state === "connected") setImmediate(() => connected(peer));
822
+ if (state === "failed" || state === "closed") setImmediate(() => closePeer(id, state === "failed" ? "ICE failed" : "closed by the page's end"));
823
+ });
824
+ pc.onDataChannel((dc) => {
825
+ const label = dc.getLabel();
826
+ if (label === "ctl") control(peer, dc);
827
+ else if (label === "term") term(peer, dc);
828
+ else dc.close();
829
+ });
830
+ try {
831
+ pc.setRemoteDescription(sdp, "offer");
832
+ } catch (err) {
833
+ closePeer(id, message(err));
834
+ return;
835
+ }
836
+ for (const c of queued) try {
837
+ pc.addRemoteCandidate(c.candidate, c.mid);
838
+ } catch {}
839
+ }
840
+ /** Whether a peer that asked for the router's port is connected. */
841
+ function portInUse() {
842
+ for (const p of peers.values()) if (p.usesPort && p.state === "connected") return true;
843
+ return false;
844
+ }
845
+ /** Connected: the pair in use, as this end sees it — the page's address
846
+ * too, which the page itself cannot see (a browser keeps its own host
847
+ * address behind an mDNS name). */
848
+ function connected(peer, tries = 0) {
849
+ if (peers.get(peer.id) !== peer) return;
850
+ let pair = null;
851
+ try {
852
+ pair = peer.pc.getSelectedCandidatePair();
853
+ } catch {}
854
+ if (!pair) {
855
+ if (tries < 5) setTimeout(() => connected(peer, tries + 1), 200);
856
+ return;
857
+ }
858
+ say({
859
+ t: "pair",
860
+ id: peer.id,
861
+ page: side(pair.remote),
862
+ server: side(pair.local)
863
+ });
864
+ }
865
+ /** Closed, and said so unless the server asked (`why` null). */
866
+ function closePeer(id, why) {
867
+ early.delete(id);
868
+ const peer = peers.get(id);
869
+ if (!peer) return;
870
+ peers.delete(id);
871
+ if (!peers.size) busyAt = Date.now();
872
+ if (why !== null) say({
873
+ t: "closed",
874
+ id,
875
+ why
876
+ });
877
+ clearInterval(peer.timer);
878
+ for (const chan of peer.channels) chan.lost();
879
+ try {
880
+ peer.pc.close();
881
+ } catch {}
882
+ }
883
+ /** The page's pings: echoed as they came, from here (the server is not
884
+ * asked), and heard as its liveness. */
885
+ function control(peer, dc) {
886
+ dc.onMessage((msg) => {
887
+ peer.heard = Date.now();
888
+ if (typeof msg !== "string") return;
889
+ try {
890
+ dc.sendMessage(msg);
891
+ } catch {}
892
+ });
893
+ }
894
+ /** A terminal: its first message is the /term path, which the server is
895
+ * handed with a channel number; from then on its frames go both ways. */
896
+ function term(peer, dc) {
897
+ let chan = null;
898
+ dc.onMessage((msg) => {
899
+ peer.heard = Date.now();
900
+ if (chan) return chan.receive(msg);
901
+ if (typeof msg !== "string" || !msg.startsWith("/term/")) {
902
+ dc.close();
903
+ return;
904
+ }
905
+ const made = new Chan(++chanCount, peer, dc, codecOf(msg));
906
+ chan = made;
907
+ say({
908
+ t: "open",
909
+ chan: made.id,
910
+ peer: peer.id,
911
+ path: msg
912
+ });
913
+ });
914
+ dc.onClosed(() => chan?.lost());
915
+ }
916
+ function codecOf(path) {
917
+ const asked = new URLSearchParams(path.split("?")[1] ?? "").get("codec");
918
+ return asked === "brotli" || asked === "deflate-raw" ? asked : null;
919
+ }
920
+ /** One compressor for a channel's whole life, flushed at every message so
921
+ * nothing waits on the next: a terminal's output repeats itself across
922
+ * frames (a redrawn status line, a prompt), which a running context finds
923
+ * and a per-frame one cannot. Brotli at quality 5 with a 1 MiB window
924
+ * (lgwin 20: 22 made no recording smaller); deflate at its default level
925
+ * and full 32 KiB window. */
926
+ function compressor(codec) {
927
+ const c = zlib.constants;
928
+ return codec === "brotli" ? zlib.createBrotliCompress({
929
+ flush: c.BROTLI_OPERATION_FLUSH,
930
+ chunkSize: PIECE,
931
+ params: {
932
+ [c.BROTLI_PARAM_QUALITY]: 5,
933
+ [c.BROTLI_PARAM_LGWIN]: 20,
934
+ [c.BROTLI_PARAM_MODE]: c.BROTLI_MODE_TEXT
935
+ }
936
+ }) : zlib.createDeflateRaw({
937
+ flush: c.Z_SYNC_FLUSH,
938
+ windowBits: 15,
939
+ memLevel: 8,
940
+ chunkSize: PIECE
941
+ });
942
+ }
943
+ /** Marks a message of the server's own (JSON) in a compressed stream: a
944
+ * byte UTF-8 never holds, so output needs no framing around it. */
945
+ var MARK = 255;
946
+ /** Channels whose queued output the server has not heard the last of. */
947
+ var reporting = /* @__PURE__ */ new Set();
948
+ var reportTimer;
949
+ var reportSoon = false;
950
+ /**
951
+ * A terminal's DataChannel, the network end of the server's TermChannel
952
+ * for it (index.ts `ipcChannel`). A DataChannel has no close code, so each
953
+ * end says its code in a last message (`{bye}`) before closing; one that
954
+ * closes without a word was lost. It has no pings either: its liveness is
955
+ * the peer's (ICE state, and the page's pings on its control channel).
956
+ *
957
+ * With a codec, everything the server sends on it is one compressed
958
+ * stream (keys up from the page are not: they are small, and the page's
959
+ * CPU is the scarcer): output as its UTF-8, the server's own messages each
960
+ * between two MARKs, so the page reads them back in the one order they
961
+ * were sent. The stream sits under the attach's resume: offsets and the
962
+ * replay are characters, and a new channel starts a new stream. It ends
963
+ * properly before a `bye`'d close, so the page can tell the end from a
964
+ * loss. Without one (a browser with no DecompressionStream), output is
965
+ * text frames and the server's messages binary, as on a websocket.
966
+ *
967
+ * The server's side of it counts what it sent and hears back what was
968
+ * taken and what is still queued here (`drain`), so the attach's read
969
+ * pauses on a slow page as it would on a slow websocket.
970
+ */
971
+ var Chan = class {
972
+ id;
973
+ peer;
974
+ dc;
975
+ packed;
976
+ code = null;
977
+ done = false;
978
+ closing = false;
979
+ /** Output bytes taken off the pipe. */
980
+ taken = 0;
981
+ constructor(id, peer, dc, codec) {
982
+ this.id = id;
983
+ this.peer = peer;
984
+ this.dc = dc;
985
+ chans.set(id, this);
986
+ peer.channels.add(this);
987
+ this.packed = codec ? compressor(codec) : null;
988
+ this.packed?.on("data", (chunk) => {
989
+ try {
990
+ if (dc.isOpen()) dc.sendMessageBinary(chunk);
991
+ } catch {}
992
+ this.report();
993
+ });
994
+ this.packed?.on("error", () => this.shut());
995
+ }
996
+ get queued() {
997
+ try {
998
+ return this.dc.bufferedAmount() + (this.packed?.writableLength ?? 0);
999
+ } catch {
1000
+ return 0;
1001
+ }
1002
+ }
1003
+ get open() {
1004
+ if (this.done || this.closing) return false;
1005
+ try {
1006
+ return this.dc.isOpen();
1007
+ } catch {
1008
+ return false;
1009
+ }
1010
+ }
1011
+ /** From the server: output (text, its UTF-8) or its own word (binary). */
1012
+ output(kind, payload) {
1013
+ this.taken += payload.length;
1014
+ this.report();
1015
+ if (!this.open) return;
1016
+ if (this.packed) {
1017
+ this.packed.write(kind === 1 ? payload : this.mark(payload));
1018
+ return;
1019
+ }
1020
+ try {
1021
+ if (kind === 2) this.dc.sendMessageBinary(payload);
1022
+ else for (const piece of pieces(payload.toString())) this.dc.sendMessage(piece);
1023
+ } catch {}
1024
+ }
1025
+ /** From the page: keys (binary) or its control (text), or its bye. */
1026
+ receive(msg) {
1027
+ if (this.done) return;
1028
+ if (typeof msg === "string") {
1029
+ const bye = byeOf(msg);
1030
+ if (bye !== null) {
1031
+ this.code = bye;
1032
+ return;
1033
+ }
1034
+ wire.send(1, this.id, Buffer.from(msg));
1035
+ } else wire.send(2, this.id, Buffer.isBuffer(msg) ? msg : Buffer.from(msg));
1036
+ }
1037
+ close(code) {
1038
+ if (this.closing || this.done) return;
1039
+ const said = code !== void 0 && this.open;
1040
+ this.closing = true;
1041
+ if (!said) return this.shut();
1042
+ const bye = Buffer.from(JSON.stringify({ bye: code }));
1043
+ if (!this.packed) {
1044
+ try {
1045
+ this.dc.sendMessageBinary(bye);
1046
+ } catch {}
1047
+ return this.shut();
1048
+ }
1049
+ this.packed.once("end", () => this.shut());
1050
+ this.packed.end(this.mark(bye));
1051
+ setTimeout(() => this.shut(), 2e3).unref();
1052
+ }
1053
+ terminate() {
1054
+ this.shut();
1055
+ }
1056
+ /** Gone, with whatever code the page said before it went (1006 when it
1057
+ * said none: lost, and the attach is held). */
1058
+ lost() {
1059
+ if (this.done) return;
1060
+ this.done = true;
1061
+ chans.delete(this.id);
1062
+ reporting.delete(this);
1063
+ this.peer.channels.delete(this);
1064
+ this.packed?.destroy();
1065
+ say({
1066
+ t: "lost",
1067
+ chan: this.id,
1068
+ code: this.code ?? 1006
1069
+ });
1070
+ }
1071
+ /** Tells the server how far its output has got: at once for the bytes
1072
+ * taken, then every REPORT_MS while any is still queued. */
1073
+ report() {
1074
+ reporting.add(this);
1075
+ if (reportSoon) return;
1076
+ reportSoon = true;
1077
+ setImmediate(sendReports);
1078
+ }
1079
+ shut() {
1080
+ if (this.done) return;
1081
+ try {
1082
+ this.dc.close();
1083
+ } catch {}
1084
+ setImmediate(() => this.lost());
1085
+ }
1086
+ mark(json) {
1087
+ return Buffer.concat([
1088
+ Buffer.of(MARK),
1089
+ json,
1090
+ Buffer.of(MARK)
1091
+ ]);
1092
+ }
1093
+ /** What the last report said was queued, to stop reporting a channel
1094
+ * once it said nothing was. */
1095
+ said = -1;
1096
+ drain() {
1097
+ const buffered = this.queued;
1098
+ say({
1099
+ t: "drain",
1100
+ chan: this.id,
1101
+ done: this.taken,
1102
+ buffered
1103
+ });
1104
+ this.said = buffered;
1105
+ }
1106
+ };
1107
+ function sendReports() {
1108
+ reportSoon = false;
1109
+ for (const chan of reporting) {
1110
+ chan.drain();
1111
+ if (chan.said === 0) reporting.delete(chan);
1112
+ }
1113
+ if (reporting.size && !reportTimer) reportTimer = setInterval(() => {
1114
+ if (!reporting.size) {
1115
+ clearInterval(reportTimer);
1116
+ reportTimer = void 0;
1117
+ return;
1118
+ }
1119
+ sendReports();
1120
+ }, REPORT_MS);
1121
+ }
1122
+ function byeOf(text) {
1123
+ if (!text.startsWith("{\"bye\":")) return null;
1124
+ try {
1125
+ const bye = JSON.parse(text).bye;
1126
+ return Number.isInteger(bye) ? bye : null;
1127
+ } catch {
1128
+ return null;
1129
+ }
1130
+ }
1131
+ /** `text` in pieces of at most PIECE characters, never splitting a
1132
+ * surrogate pair: each piece is encoded on its own, and half a pair would
1133
+ * arrive as U+FFFD, a character the page's count would not agree with. */
1134
+ function* pieces(text) {
1135
+ if (text.length <= PIECE) {
1136
+ yield text;
1137
+ return;
1138
+ }
1139
+ for (let i = 0; i < text.length;) {
1140
+ let end = Math.min(i + PIECE, text.length);
1141
+ const last = text.charCodeAt(end - 1);
1142
+ if (end < text.length && last >= 55296 && last <= 56319) end--;
1143
+ yield text.slice(i, end);
1144
+ i = end;
1145
+ }
1146
+ }
1147
+ /** Closes every peer (their pages fall back to websockets at once rather
1148
+ * than when their paths time out), gives back a router port it holds
1149
+ * (bounded: a router that does not answer cannot hold this up), and
1150
+ * exits, which frees the UDP port. */
1151
+ async function leave(why) {
1152
+ if (leaving) return;
1153
+ leaving = true;
1154
+ for (const id of [...peers.keys()]) closePeer(id, null);
1155
+ try {
1156
+ anchor?.close();
1157
+ } catch {}
1158
+ if (upnpStatus().state !== "off") await Promise.race([upnpRelease().catch(() => {}), new Promise((r) => setTimeout(r, 1500))]);
1159
+ say({
1160
+ t: "exit",
1161
+ why
1162
+ });
1163
+ pipe.end(() => process.exit(0));
1164
+ setTimeout(() => process.exit(0), 500).unref();
1165
+ }
1166
+ function message(err) {
1167
+ return err instanceof Error ? err.message : String(err);
1168
+ }
1169
+ //#endregion
1170
+ export {};