@astralyn/sash 0.1.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 (339) hide show
  1. package/CHANGELOG.md +184 -0
  2. package/LICENSE +21 -0
  3. package/README.md +71 -0
  4. package/THIRD_PARTY_NOTICES.md +54 -0
  5. package/dist/api.js +103 -0
  6. package/dist/browser.js +25 -0
  7. package/dist/cli-errors.js +15 -0
  8. package/dist/cli.js +122 -0
  9. package/dist/commands/lifecycle.js +50 -0
  10. package/dist/commands/logs.js +54 -0
  11. package/dist/commands/shared.js +8 -0
  12. package/dist/commands/status.js +39 -0
  13. package/dist/commands/update.js +5 -0
  14. package/dist/commands/upgrade.js +43 -0
  15. package/dist/commands/web.js +33 -0
  16. package/dist/contracts.js +412 -0
  17. package/dist/core-config-validation.js +72 -0
  18. package/dist/core-install-record.js +55 -0
  19. package/dist/core-install-transaction.js +114 -0
  20. package/dist/core-update-coordination.js +105 -0
  21. package/dist/core-update-service.js +245 -0
  22. package/dist/core-update.js +804 -0
  23. package/dist/core-version.js +18 -0
  24. package/dist/core.js +206 -0
  25. package/dist/daemon/app.js +157 -0
  26. package/dist/daemon/context.js +40 -0
  27. package/dist/daemon/entry.js +67 -0
  28. package/dist/daemon/errors.js +47 -0
  29. package/dist/daemon/handlers/core.js +17 -0
  30. package/dist/daemon/handlers/daemon.js +66 -0
  31. package/dist/daemon/handlers/profiles.js +57 -0
  32. package/dist/daemon/handlers/proxy.js +12 -0
  33. package/dist/daemon/handlers/settings.js +48 -0
  34. package/dist/daemon/router.js +312 -0
  35. package/dist/daemon/scheduler.js +33 -0
  36. package/dist/daemon/server.js +121 -0
  37. package/dist/daemon-auth.js +71 -0
  38. package/dist/daemon-client.js +55 -0
  39. package/dist/daemon-entry.js +13 -0
  40. package/dist/daemon-http.js +93 -0
  41. package/dist/daemon-lifecycle.js +378 -0
  42. package/dist/daemon-proxy.js +176 -0
  43. package/dist/daemon-static.js +106 -0
  44. package/dist/daemon.js +3 -0
  45. package/dist/error-utils.js +14 -0
  46. package/dist/fs-atomic.js +123 -0
  47. package/dist/github.js +187 -0
  48. package/dist/http.js +282 -0
  49. package/dist/json-shape.js +17 -0
  50. package/dist/log-follow.js +280 -0
  51. package/dist/log.js +17 -0
  52. package/dist/managed-state-transaction.js +377 -0
  53. package/dist/mihomo-config.js +334 -0
  54. package/dist/node-version-guard.js +8 -0
  55. package/dist/offline-mutation.js +58 -0
  56. package/dist/paths.js +57 -0
  57. package/dist/process.js +572 -0
  58. package/dist/profile-migration.js +101 -0
  59. package/dist/profile-service.js +667 -0
  60. package/dist/profiles.js +182 -0
  61. package/dist/runtime-lifecycle.js +205 -0
  62. package/dist/runtime-owner.js +87 -0
  63. package/dist/runtime-recovery.js +31 -0
  64. package/dist/sash-client.js +163 -0
  65. package/dist/settings-service.js +242 -0
  66. package/dist/settings.js +314 -0
  67. package/dist/state-lock.js +331 -0
  68. package/dist/status.js +279 -0
  69. package/dist/supervisor.js +301 -0
  70. package/dist/sysproxy/common.js +99 -0
  71. package/dist/sysproxy/darwin.js +287 -0
  72. package/dist/sysproxy/factory.js +71 -0
  73. package/dist/sysproxy/gnome.js +181 -0
  74. package/dist/sysproxy/legacy.js +58 -0
  75. package/dist/sysproxy/snapshot.js +284 -0
  76. package/dist/sysproxy/types.js +24 -0
  77. package/dist/sysproxy/windows.js +216 -0
  78. package/dist/sysproxy.js +8 -0
  79. package/dist/system-proxy-manager.js +460 -0
  80. package/dist/tun-guidance.js +11 -0
  81. package/dist/ui/assets/05129d5ad91a6181d272cc3d031363d6-DJkDXMrK.woff2 +0 -0
  82. package/dist/ui/assets/0526ebf94418638dfd5b6cd9b06c07d4-BzWe4Lji.woff2 +0 -0
  83. package/dist/ui/assets/053f2647a78f659f6f8a3ae8436bd265-BocXYDl5.woff2 +0 -0
  84. package/dist/ui/assets/078f666863424d3fd2d98e3ea528704c-DQ_QCvOt.woff2 +0 -0
  85. package/dist/ui/assets/07f3ff379663ffa0222c12f241421dd9-Cma7oDlr.woff2 +0 -0
  86. package/dist/ui/assets/08427b8d865853bbd7c439321b26495f-CJmC8fWx.woff2 +0 -0
  87. package/dist/ui/assets/095224e4db29d40c54098da02b266b0a-DjsP0nQn.woff2 +0 -0
  88. package/dist/ui/assets/0ada18ca45940b08accde5c61ee871e3-E1BZD3RX.woff2 +0 -0
  89. package/dist/ui/assets/0be242294f7d791af850c6df38ac78a0-2cL6Ntwf.woff2 +0 -0
  90. package/dist/ui/assets/0bf18c938d0c27cce06c39f855405fdb-CruLZZZJ.woff2 +0 -0
  91. package/dist/ui/assets/0cfb3e9f4edf60839c37dc83e6f75ea3-DzAtRHvi.woff2 +0 -0
  92. package/dist/ui/assets/0fbbd340d0467dfe3101d58846f30cb0-gtFcO250.woff2 +0 -0
  93. package/dist/ui/assets/1063fa2dc7334d1f383e30824e016eae-B7U-kD-0.woff2 +0 -0
  94. package/dist/ui/assets/109e26e62ddc85d946e699464afbece7-BLnUjAEV.woff2 +0 -0
  95. package/dist/ui/assets/12a443b0ae9748a993b5395f8fad412b-Btjff2Lz.woff2 +0 -0
  96. package/dist/ui/assets/135c4621de7a4d85d852e03d416f13ee-DdSuW36e.woff2 +0 -0
  97. package/dist/ui/assets/13aa415469ee761c968749b1c0c56ec6-Dwe-Wa_w.woff2 +0 -0
  98. package/dist/ui/assets/1435b1ad447bd9c0e8bc52035c9dcfa8-C05ldXtw.woff2 +0 -0
  99. package/dist/ui/assets/175e0915aa874e7446b79b6bc9b00721-Dp0-l77L.woff2 +0 -0
  100. package/dist/ui/assets/1876e7d02187f006e2b7f769391c3089-CNjpRdap.woff2 +0 -0
  101. package/dist/ui/assets/1886dc284b3b845e3b5fe04aa2107276-Bh_wHwL1.woff2 +0 -0
  102. package/dist/ui/assets/19a3dda6611aaf5d787403bd1159fe15-zoEBvdpA.woff2 +0 -0
  103. package/dist/ui/assets/1afa74e14da193125e45d4783d358a4f-BgjSTENX.woff2 +0 -0
  104. package/dist/ui/assets/1cd26a6a6d784720b4a43053e9058bcf-TAP3dvNi.woff2 +0 -0
  105. package/dist/ui/assets/1ce19bddc3beab385a863b1e05685678-BCdXitZw.woff2 +0 -0
  106. package/dist/ui/assets/1d3459ac6578c80502bdb876a4824a93-CSwdnEGP.woff2 +0 -0
  107. package/dist/ui/assets/1d479e364dab4af01c539f0a6a7ac036-ZFI-djGt.woff2 +0 -0
  108. package/dist/ui/assets/1dcea2408d17fcec4197adce50f8b32d-CnK2xXR-.woff2 +0 -0
  109. package/dist/ui/assets/1e855385699999e24a33f7df8aed0489-DGFHkDVQ.woff2 +0 -0
  110. package/dist/ui/assets/1f55944b8f7972580e3029694462a6b0-CB7ogVJ9.woff2 +0 -0
  111. package/dist/ui/assets/1f96eda70929089762dc3c4feb33f9c8-DtSQ6YOf.woff2 +0 -0
  112. package/dist/ui/assets/1fb275532caadc746455881b31da27c4-BHFTsg74.woff2 +0 -0
  113. package/dist/ui/assets/20e679bdc00d06f802ea71fd9154b284-D9gRjq_v.woff2 +0 -0
  114. package/dist/ui/assets/25aff66c4a9f8cb801cbd525f4a923e4-Dgi87hP7.woff2 +0 -0
  115. package/dist/ui/assets/28e6700380b023b23e77f2d75aee624f-b19D855Q.woff2 +0 -0
  116. package/dist/ui/assets/2a1c50aa9cd99ce3a0197f5d49948e63-DrztdXCB.woff2 +0 -0
  117. package/dist/ui/assets/2b4e0ae0bf3af610b97717dfd66a012d-CBoapvha.woff2 +0 -0
  118. package/dist/ui/assets/2be2b7a672b616bc2f07059a67bfaad8-BUR0aNML.woff2 +0 -0
  119. package/dist/ui/assets/2e0a358ada78ea98ec8ba86002a27eac-DvgbSr9v.woff2 +0 -0
  120. package/dist/ui/assets/2e1cf63a2a22a50516c49b1dc521ea8a-CmltIxig.woff2 +0 -0
  121. package/dist/ui/assets/2e4481801925fcb6573c4337c73f15d7-DYOoCk-m.woff2 +0 -0
  122. package/dist/ui/assets/2fe5e634c568cd874f94d24b8f02bfd1-DcEHY35G.woff2 +0 -0
  123. package/dist/ui/assets/309c33f63f29887fb557115aa24d4bb5-CUgqrXIj.woff2 +0 -0
  124. package/dist/ui/assets/3158f45c959e1b346b0bbd6f3193dbf0-kimcClqp.woff2 +0 -0
  125. package/dist/ui/assets/316adbc8037a36f8d804637b053bbe5e-CjbZmJf9.woff2 +0 -0
  126. package/dist/ui/assets/319e9631bc29fc7235b70fc9aac43cd9-IV6yZuHZ.woff2 +0 -0
  127. package/dist/ui/assets/32054f9bb88d9431b1cdf023f770e614-CYrpCgqK.woff2 +0 -0
  128. package/dist/ui/assets/3245fa1aac6338c1d9d04befb7c346f8-CzvPhVPA.woff2 +0 -0
  129. package/dist/ui/assets/326c730f98afa476057957f2743c81e2-GPHJ3Fpu.woff2 +0 -0
  130. package/dist/ui/assets/33885558dc783bf9bae6c5d4668fef07-D33us0yR.woff2 +0 -0
  131. package/dist/ui/assets/33cd11f5e61556e4618801c8231f2744-CoR_Tv3X.woff2 +0 -0
  132. package/dist/ui/assets/34a170749cb7e6c5851acc510882af6b-BCZwsihk.woff2 +0 -0
  133. package/dist/ui/assets/3538de2d387591258d7e9908aad803b9-CewOkERZ.woff2 +0 -0
  134. package/dist/ui/assets/38d7b029c036080067a2e94211098171-CettR5Qv.woff2 +0 -0
  135. package/dist/ui/assets/393f47efeefc9f9e9cca83eb10a82c69-Brhz4z1y.woff2 +0 -0
  136. package/dist/ui/assets/396a51f2b274d01f6ba606a94f3e5fe2-DgRk-jtI.woff2 +0 -0
  137. package/dist/ui/assets/3991bf955d4c925e84b4a6542760d4d5-II4HtRkQ.woff2 +0 -0
  138. package/dist/ui/assets/39b6cb6ae8d1ea0e9832b88d2c21ef99-CbJm-q6b.woff2 +0 -0
  139. package/dist/ui/assets/3a1133157fbd259f1a51aaa9f8418a26-3FmUjjvi.woff2 +0 -0
  140. package/dist/ui/assets/3a34cff07dca7a65ed2486be19229290-B7ml2yBN.woff2 +0 -0
  141. package/dist/ui/assets/3c339dd7c27e6b8a06eea431733e076f-dGQqXaEj.woff2 +0 -0
  142. package/dist/ui/assets/3d074ea67f60953b816ce7cf5d87a980-B5VkPOmg.woff2 +0 -0
  143. package/dist/ui/assets/3d38f54dcf4a9f4a50165d5849becf71-CVDtFSZn.woff2 +0 -0
  144. package/dist/ui/assets/3e3fc9a19a27f94e4ff1b815b55b2671-Ci736GLi.woff2 +0 -0
  145. package/dist/ui/assets/3e8abb1f606f29e2f0a8a8a6b702d263-C5sOSf5L.woff2 +0 -0
  146. package/dist/ui/assets/3f0ab4e1cd99bdbfdf9acbc8f4bcb9ce-0DcdTLxV.woff2 +0 -0
  147. package/dist/ui/assets/3f30c0482650345c981d66920f56ab61-CTD2CsZI.woff2 +0 -0
  148. package/dist/ui/assets/41a9aa12ad9a60758b304c1a998fb781-BTQUDLf1.woff2 +0 -0
  149. package/dist/ui/assets/424010b2bc5cee540b92b28da49b61bf-DT8upW2M.woff2 +0 -0
  150. package/dist/ui/assets/42fbdafff2d44b76c6665d51bb2cee9d-_UrojhXD.woff2 +0 -0
  151. package/dist/ui/assets/4341db13c311b3cb0eaaf6b4c71b89e5-CzjA7MzG.woff2 +0 -0
  152. package/dist/ui/assets/434db5a796128131fde78d4389d14b71-AeNdWKua.woff2 +0 -0
  153. package/dist/ui/assets/43cb51fc70a15274d49e2d956c5ec3b4-BQbufd96.woff2 +0 -0
  154. package/dist/ui/assets/43e79de1c7815f21efbc966ff96fcbd6-DlCQvOsQ.woff2 +0 -0
  155. package/dist/ui/assets/43faf6a8a3cde1088bd5f7c830890a89-SvDzF3nA.woff2 +0 -0
  156. package/dist/ui/assets/44731ae21d7690ce538d0f8242755833-CEmu9Z88.woff2 +0 -0
  157. package/dist/ui/assets/45324b2a64cc0c0db797d5c09e239d34-BuFudtvM.woff2 +0 -0
  158. package/dist/ui/assets/4584df05d00aa75ae6e1f850fdc9df1d-GNHt6xwM.woff2 +0 -0
  159. package/dist/ui/assets/48d539539e98481fb511a761031c59fd-Cd9gdi8B.woff2 +0 -0
  160. package/dist/ui/assets/49a1fb9fc6a35796b2e0d3539088ede8-CD1cRDOy.woff2 +0 -0
  161. package/dist/ui/assets/4bec02eb0c2371e59bfb7d41a141ea1e-CmBnNPo0.woff2 +0 -0
  162. package/dist/ui/assets/4c5a81ef5683f6fbf9935e640de804e7-B4wQxzVs.woff2 +0 -0
  163. package/dist/ui/assets/4f11c9c75e86214b7f69ddee3a9f8076-DHsQ2xpE.woff2 +0 -0
  164. package/dist/ui/assets/50434b8ff88e27101d1c67ba953ee873-PH_hIqxu.woff2 +0 -0
  165. package/dist/ui/assets/5107fd5153d52c6a3f8d43ff214dd6c2-ls3eo5Qd.woff2 +0 -0
  166. package/dist/ui/assets/520cb7d3683dee99e767650efc0aa5b1-Lkd4z-9S.woff2 +0 -0
  167. package/dist/ui/assets/5455087df2f091b7bf3d38c5a102a2e7-QNkiOxwe.woff2 +0 -0
  168. package/dist/ui/assets/556450fdf0978d329cf8277e3b71dcdb-CtypGAPw.woff2 +0 -0
  169. package/dist/ui/assets/5a1b5c1b71cad50c51a79a7f4825e6e6-DQA407Ec.woff2 +0 -0
  170. package/dist/ui/assets/5ba02e809d2ef4f7639178e7a7d654c0-Dty0omtl.woff2 +0 -0
  171. package/dist/ui/assets/5bd654afe48e094f95e5142e4eaadc75-D9pUq71R.woff2 +0 -0
  172. package/dist/ui/assets/5c4c3ca0a3f4115879a61a96d02e3efc-RcVLBj31.woff2 +0 -0
  173. package/dist/ui/assets/5c737d740638e8d764f12c1e5c1cd144-D2Npjq-N.woff2 +0 -0
  174. package/dist/ui/assets/5e78019102102aa5741aa591313fd5b5-V7Azrw-2.woff2 +0 -0
  175. package/dist/ui/assets/6287cacac64e1849941f7b7d7e6316c5-DzkbPOID.woff2 +0 -0
  176. package/dist/ui/assets/643df7194160d54edc48d16d76e5ba74-BKmmIRjw.woff2 +0 -0
  177. package/dist/ui/assets/65d3b214c69ff6786fd1cab06f8394b7-B0cRUJAr.woff2 +0 -0
  178. package/dist/ui/assets/6698103c11bfe260d44fcf00c6d8b623-DVWYOeyb.woff2 +0 -0
  179. package/dist/ui/assets/692e07b3a609a8f1a21c15c45631f348-DJW5LcZH.woff2 +0 -0
  180. package/dist/ui/assets/694e87456af181279c2bfd551aa3c6e3-Cu7kRuJB.woff2 +0 -0
  181. package/dist/ui/assets/6b88066bdc7a6e8bc823128dbd0f6f3b-BG38UZSq.woff2 +0 -0
  182. package/dist/ui/assets/6d6f36f15381826d113832bfdd41b021-Bgh7YPiq.woff2 +0 -0
  183. package/dist/ui/assets/6e22361162bd0a5cd189ca3c98d23d52-DqnptHqF.woff2 +0 -0
  184. package/dist/ui/assets/6e884dd501b8da24ecd37425a9b3d51d-BVNqra8s.woff2 +0 -0
  185. package/dist/ui/assets/71200dbc7497fbafa28db5cfeae74acb-B6xMqoC6.woff2 +0 -0
  186. package/dist/ui/assets/72a85441a9757146440e3985f3b02075-CzTSlCPZ.woff2 +0 -0
  187. package/dist/ui/assets/7472d7e02045dea3ff288eac850e38eb-CboJEy4X.woff2 +0 -0
  188. package/dist/ui/assets/795ffa953bea666763ae79ac88aa7e5a-C6UsoDmi.woff2 +0 -0
  189. package/dist/ui/assets/7aa8647d6c52d249169b73417aa8e444-kJejhQ0-.woff2 +0 -0
  190. package/dist/ui/assets/7be6b794fc233ccd8b5769496301313a-Ba2iZC0a.woff2 +0 -0
  191. package/dist/ui/assets/7c0748f04d52086809c9f551c5aaded7-DSzO_ybF.woff2 +0 -0
  192. package/dist/ui/assets/7c858bb53a7652227fab8cf9753d3e1f-Bi9fbvIF.woff2 +0 -0
  193. package/dist/ui/assets/7e7026091b4e6061b05963bdb3a7f944-CrmRDJpc.woff2 +0 -0
  194. package/dist/ui/assets/7f80369a7479d568ab8bf492000282bd-CqcI5ZOp.woff2 +0 -0
  195. package/dist/ui/assets/834ce2f2571aaa26e47a618c4ad55e58-DZfWJrxo.woff2 +0 -0
  196. package/dist/ui/assets/834faab602d9c795341bc7dfd6578fe5-B1VeBVWh.woff2 +0 -0
  197. package/dist/ui/assets/8513a8eea35b460a047a16a87c36f8bb-H259w_6b.woff2 +0 -0
  198. package/dist/ui/assets/8699e8a36465bf46671dc28ce24e188c-YlkPpQ6h.woff2 +0 -0
  199. package/dist/ui/assets/86ae8881eae9eac7e9c0182c8ce6c627-Ch2r3LNs.woff2 +0 -0
  200. package/dist/ui/assets/86b3ae1be58d76f7147793f09264451b-CpgTfcn-.woff2 +0 -0
  201. package/dist/ui/assets/8769f8d7d45adcfcbd3f80460d22720d-DV_yqcTo.woff2 +0 -0
  202. package/dist/ui/assets/87bb0ceea9f5515490c832bdf6ebf641-CJpxd8e6.woff2 +0 -0
  203. package/dist/ui/assets/8928b9a50b5efbaaafa039674a3c7301-C-42WRD6.woff2 +0 -0
  204. package/dist/ui/assets/899f2c2a36cbeac473a7caeeaabc46b1-xrIcXJXU.woff2 +0 -0
  205. package/dist/ui/assets/8a075d4043e6bf66740808b492186438-Dq0346Qm.woff2 +0 -0
  206. package/dist/ui/assets/8b86676936792154c53205ab5d90b797-BGH_CGsg.woff2 +0 -0
  207. package/dist/ui/assets/8c63ca479653200a0e12f428242e1171-CsBTcPwY.woff2 +0 -0
  208. package/dist/ui/assets/8c9c7ff8342f5fed60e73c4a7ad67325-CGSYpt52.woff2 +0 -0
  209. package/dist/ui/assets/8ca655fcc5d9cf33d49815e231f3f356-b8PyEeZg.woff2 +0 -0
  210. package/dist/ui/assets/8e96f0f9385ef826f900587d0fc18b3d-D7GIG6Uq.woff2 +0 -0
  211. package/dist/ui/assets/8ea917252cf2c1e664f06ee5badffeea-CDZ1AFr0.woff2 +0 -0
  212. package/dist/ui/assets/8f42f890fc37e292017446af85af2745-Qx6oGI0z.woff2 +0 -0
  213. package/dist/ui/assets/8f9589e71c53bb753749a9c412f15c01-BVR5mPJl.woff2 +0 -0
  214. package/dist/ui/assets/90e70b52a3cc2491bbb2b4c0caf0ae7e-BdwPu67j.woff2 +0 -0
  215. package/dist/ui/assets/92ac4ca42fe050c1e680c585551abd9c-CXLyuj7l.woff2 +0 -0
  216. package/dist/ui/assets/92c12bb794c491930fd06f3f97299807-u_0ognJ0.woff2 +0 -0
  217. package/dist/ui/assets/92f0b4d2bb274a801aac10fc34c1e0cc-DFHB6dyu.woff2 +0 -0
  218. package/dist/ui/assets/93247aebd628367f5518eafdb2770ce9-o1N4zvTO.woff2 +0 -0
  219. package/dist/ui/assets/940446ea160995af66a9ee223cc3d4d1-C-_7aPYO.woff2 +0 -0
  220. package/dist/ui/assets/94be430de618453b6ae27bd366827ba2-1YYf1n5N.woff2 +0 -0
  221. package/dist/ui/assets/98cd7d62095e466e55885b34f63c86f1-z33YCk0X.woff2 +0 -0
  222. package/dist/ui/assets/99049fd5cec5894cafe2a1e5e13d1e03-DNrg8nIk.woff2 +0 -0
  223. package/dist/ui/assets/99e2752b7351aaa7681b4d91db80e89a-b3hi7miY.woff2 +0 -0
  224. package/dist/ui/assets/9d1b670cbfd64716ac685dea26237d95-DAZ9U6es.woff2 +0 -0
  225. package/dist/ui/assets/CodeEditorModal-6m0TJWyF.css +1 -0
  226. package/dist/ui/assets/CodeEditorModal-CFEnWsyh.js +14 -0
  227. package/dist/ui/assets/ConnectionsView-DHo6yvU6.css +1 -0
  228. package/dist/ui/assets/ConnectionsView-DNGmZBSU.js +1 -0
  229. package/dist/ui/assets/LogsView-BoeE92VF.css +1 -0
  230. package/dist/ui/assets/LogsView-fSiaxQ13.js +1 -0
  231. package/dist/ui/assets/PaginationFooter-B3kHzRfB.js +1 -0
  232. package/dist/ui/assets/PaginationFooter-BNkpcNoB.css +1 -0
  233. package/dist/ui/assets/ProfileEditorDialog-BydoZthX.js +1 -0
  234. package/dist/ui/assets/ProfilesView-Btc1DOxE.js +2 -0
  235. package/dist/ui/assets/ProfilesView-ByqdFNHN.css +1 -0
  236. package/dist/ui/assets/RulesView-CHyynVqA.css +1 -0
  237. package/dist/ui/assets/RulesView-D9vZBiJ1.js +1 -0
  238. package/dist/ui/assets/SettingsFileDialog-CNFEAVs4.js +1 -0
  239. package/dist/ui/assets/SettingsView-BlDhZkXQ.js +2 -0
  240. package/dist/ui/assets/SettingsView-CngS3vBM.css +1 -0
  241. package/dist/ui/assets/a0f2d41645a113d821e7ff0891daad1e-XK0rDAci.woff2 +0 -0
  242. package/dist/ui/assets/a1d1345d8407dd6169f39c28ff6b1708-Bn7hMmv8.woff2 +0 -0
  243. package/dist/ui/assets/a29f51b4ebb6776e43e7492a036be28e-CrAKKz8u.woff2 +0 -0
  244. package/dist/ui/assets/a3252ac86b2c4e88bfb9baecabcad817-qnA_3LNE.woff2 +0 -0
  245. package/dist/ui/assets/a3d63a61e7eb2bb497dbc2da65bd0873-BRMJAm0m.woff2 +0 -0
  246. package/dist/ui/assets/a45eea933252dbe3529f30a0323c2661-D4kBOrXR.woff2 +0 -0
  247. package/dist/ui/assets/a4886c4ebed8c8b1f8a87b5b491a39aa-CBQdRqpF.woff2 +0 -0
  248. package/dist/ui/assets/a50b4ab745c7676d9d3ceb3c0a023a3d-CW990pgI.woff2 +0 -0
  249. package/dist/ui/assets/a5851ae4d7e58996d8bd681f80d4727c-BBytdb0t.woff2 +0 -0
  250. package/dist/ui/assets/a5cdaa96bf7d1db6c671432f9ab7bccb-slKJjgfX.woff2 +0 -0
  251. package/dist/ui/assets/a640dd2e962cf72c7fcf76234c973303-CUCWUDBY.woff2 +0 -0
  252. package/dist/ui/assets/a67e457bbd366aef5104ce743a2cf6d4-DkE8fLlE.woff2 +0 -0
  253. package/dist/ui/assets/a6c3f2257315d45aa212bd2e86a02852-CxDbXnZV.woff2 +0 -0
  254. package/dist/ui/assets/a73399a40a5f81fb7314b8b9a10cd221-DRAvJPKI.woff2 +0 -0
  255. package/dist/ui/assets/a73a64b0941803d9d807b9ec082df641-BbPpPyy5.woff2 +0 -0
  256. package/dist/ui/assets/a73d1a0a1dfd4f4234f7a0649ece4da4-BHeqktY9.woff2 +0 -0
  257. package/dist/ui/assets/a740044e77a400a450e71fcdea55523f-Co5hpBNK.woff2 +0 -0
  258. package/dist/ui/assets/a7669b29f404662c179c71b87e67c8d2-CVnuBgHy.woff2 +0 -0
  259. package/dist/ui/assets/a964a4def15811e91b05a91740a1cf37-CzcDfCRn.woff2 +0 -0
  260. package/dist/ui/assets/aac3f69132da164921fda75d2d90bfb7-B6rUZQ4_.woff2 +0 -0
  261. package/dist/ui/assets/aacd263c3bb4ba163a4d5079c159680a-DtrDLFlk.woff2 +0 -0
  262. package/dist/ui/assets/ac62ec881ded2cdb450e542b396e24d0-CRlVJvPa.woff2 +0 -0
  263. package/dist/ui/assets/aebcb9b7d4b174ab5a4b911206f24036-BuleSpgP.woff2 +0 -0
  264. package/dist/ui/assets/af70712dadc05c7b18d248996a5ab0a9-gCZSZGzb.woff2 +0 -0
  265. package/dist/ui/assets/afbd3a8a4cfaa4561b7848c28bc6c4f8-DgnqceEw.woff2 +0 -0
  266. package/dist/ui/assets/b02d3867e6e2ea921d2845b44c4a5a31-C9v4FrqD.woff2 +0 -0
  267. package/dist/ui/assets/b1b22825b930688403337d4fd7bbba62-vmMs2pJf.woff2 +0 -0
  268. package/dist/ui/assets/b3206c979bfb28d12d7ad6df58e74b9f-X1PqLaCR.woff2 +0 -0
  269. package/dist/ui/assets/b6280e42d4f11dcab70594f9d6774936-CtezImLG.woff2 +0 -0
  270. package/dist/ui/assets/b71b8cd1a70405c3dbc0e7a44055ca9a-Cd6cIOEr.woff2 +0 -0
  271. package/dist/ui/assets/b9198c4f220a4ca075f8daa4db529e50-Bgc7zlEt.woff2 +0 -0
  272. package/dist/ui/assets/baad304a58d7d42d0af29235c5e9490b-BQZYwARB.woff2 +0 -0
  273. package/dist/ui/assets/bb99e34f8784cc44f0eeadbe735dd238-UHFYI827.woff2 +0 -0
  274. package/dist/ui/assets/bc1cb0f523c8a2ff566a047aeddf90fd-BSlXQn97.woff2 +0 -0
  275. package/dist/ui/assets/bd3171e975d62612e50148bf38b2158f-CtZKzCzQ.woff2 +0 -0
  276. package/dist/ui/assets/be10640c403263dd5917ad65c4d453a6-B-rbuuja.woff2 +0 -0
  277. package/dist/ui/assets/be764e101e78b50b749aa1414d4810a7-yIPaQQih.woff2 +0 -0
  278. package/dist/ui/assets/bfa682a7f5f12945b41230c35eb859f6-CzagcI5f.woff2 +0 -0
  279. package/dist/ui/assets/bfa9753ed26f2e1f81ab7020167bd1a0-C6ZNQQ2p.woff2 +0 -0
  280. package/dist/ui/assets/branding/sash-cat.png +0 -0
  281. package/dist/ui/assets/c0e78c6ad709b19ce437677c91033489-BtwtlApg.woff2 +0 -0
  282. package/dist/ui/assets/c5a59409c610cc8c265d1691eb3f71eb-DmI_ky2X.woff2 +0 -0
  283. package/dist/ui/assets/c5ff66a1a57f2a29424e8de5d4ef3756-C9pOsaV1.woff2 +0 -0
  284. package/dist/ui/assets/c607bdee84a0e5b69721367ccc0c9c31-eoHbdXeg.woff2 +0 -0
  285. package/dist/ui/assets/c8095033941d18db730496eb4c863757-DoexTYks.woff2 +0 -0
  286. package/dist/ui/assets/c8295519e16f4cf3a350e9729f07653a-C8bAtIO_.woff2 +0 -0
  287. package/dist/ui/assets/c91f9c9f4db81afeba37c8e73afb0bfd-BGIl2iKW.woff2 +0 -0
  288. package/dist/ui/assets/c9ad9caa4afdd40766add91d321b0350-HfuiRq1k.woff2 +0 -0
  289. package/dist/ui/assets/cc4f271c90f9ea5157db9c0a4edc976b-MAjuDvHE.woff2 +0 -0
  290. package/dist/ui/assets/cdf7c3d96ba172214d7e1e9fe5e6ba8b-CUx4fWQE.woff2 +0 -0
  291. package/dist/ui/assets/cf70d5821ea7c3ca34653169ac763606-CE0T0Svc.woff2 +0 -0
  292. package/dist/ui/assets/d0d5efe92496b1ceea38e180b0ac97d3-7G71Z6v3.woff2 +0 -0
  293. package/dist/ui/assets/d1977142eceb878b06440375e64c4372-mr-7LWm-.woff2 +0 -0
  294. package/dist/ui/assets/d42feb13392b4dd9214c61b61a6e5ae8-DjnKADf_.woff2 +0 -0
  295. package/dist/ui/assets/d46a696ec6a077c6873fc39043c3c082-C62E-13k.woff2 +0 -0
  296. package/dist/ui/assets/d4c0bbf1605a8a9c36fff5a2ead81bb5-C2F6tzOj.woff2 +0 -0
  297. package/dist/ui/assets/d918d4e47697b7c752ea223e0d0d4c81-twovU2ty.woff2 +0 -0
  298. package/dist/ui/assets/d988c05da0b5624267dd825fedd133d1-Bgbc9SVH.woff2 +0 -0
  299. package/dist/ui/assets/da3a071df79531f4f033b7c8499651d0-0tgZJL_N.woff2 +0 -0
  300. package/dist/ui/assets/da8eba4acbf93048645043006de1c5f0-Ce0dmHn_.woff2 +0 -0
  301. package/dist/ui/assets/dc3f16892e20a85a8fcad2d3097e622b-8PRrFPJt.woff2 +0 -0
  302. package/dist/ui/assets/dd5f808ec691f911483a2eb60b5049bd-BhVupSbT.woff2 +0 -0
  303. package/dist/ui/assets/e2b04534cc697a76afd70e885119cb8c-DO303iCb.woff2 +0 -0
  304. package/dist/ui/assets/e307280eb2fb7809adee14ff0d247d32-DHMLXvSs.woff2 +0 -0
  305. package/dist/ui/assets/e3b484bec850c669094f713570870b3a-BI_9kRw8.woff2 +0 -0
  306. package/dist/ui/assets/e7b1d196949b7530cbda0ad956fbb2cc-i2m2Id0q.woff2 +0 -0
  307. package/dist/ui/assets/e7c2e5fb387880508811cadc2926e13a-BfROXX4U.woff2 +0 -0
  308. package/dist/ui/assets/e8a5fe283c8b9e173097bee0b5757ab6-BvfjDjYP.woff2 +0 -0
  309. package/dist/ui/assets/e8e3465d48435612e9c3509be3984188-BeUPU5-9.woff2 +0 -0
  310. package/dist/ui/assets/e93903f4604b007fc1b7134a74f5c534-B1M4h46T.woff2 +0 -0
  311. package/dist/ui/assets/eb0994906efd969102d522b6d8b3aff2-i9SHU5CJ.woff2 +0 -0
  312. package/dist/ui/assets/eb32eee23ceadd3fc4eb5763d3573f57-B3h9kgf3.woff2 +0 -0
  313. package/dist/ui/assets/ebadbc005027fb3e2b1c2d15be38c66b-qVQSBwBu.woff2 +0 -0
  314. package/dist/ui/assets/ecbbb464ff6934b5012cf33c80c7a907-CBEPqZfS.woff2 +0 -0
  315. package/dist/ui/assets/ed79ac25c014b6434f22fe1e2549bc26-mWhz7hkU.woff2 +0 -0
  316. package/dist/ui/assets/eed64f40443554cb9f65edaca176abb0-Cqw29XTm.woff2 +0 -0
  317. package/dist/ui/assets/efc749471476cce09e12e7038c757950-BhSWPf1Q.woff2 +0 -0
  318. package/dist/ui/assets/f2a7c2cb375662aa93d8f31116d39c20-D1zsMYCW.woff2 +0 -0
  319. package/dist/ui/assets/f5ba0fb4332ccceadc481e47134023d3-CAKMPiu5.woff2 +0 -0
  320. package/dist/ui/assets/f6326eb5031e18478aa4588598f96965-Dj3uu55n.woff2 +0 -0
  321. package/dist/ui/assets/f8678cbaab9caef95e1986731dcbd92f-CnIuR3EN.woff2 +0 -0
  322. package/dist/ui/assets/f8d9d55e2a811e9839c9af5f8f2d62ac-DIUQRBsH.woff2 +0 -0
  323. package/dist/ui/assets/f98b117e363eb51f4d7c7d1056da7688-O9MtqjIW.woff2 +0 -0
  324. package/dist/ui/assets/f9ab5d9163148ceb211e5b4f15a3d8e0-Dtqse-AT.woff2 +0 -0
  325. package/dist/ui/assets/fa69ce224ff0a6c0ff97b10a829779c2-pX45dCNY.woff2 +0 -0
  326. package/dist/ui/assets/fc38d1da3930d39dd8e286f708105765-DmbOHwTI.woff2 +0 -0
  327. package/dist/ui/assets/fe03c1dfaffc1cad3521fe55f1541d6b-BW8aEqCj.woff2 +0 -0
  328. package/dist/ui/assets/fe0b2421eb4d1f4f8949c602871a317c-BsBLV6jH.woff2 +0 -0
  329. package/dist/ui/assets/fe9c75d0cb3d808023412d26dfc9d388-eXjcgUPI.woff2 +0 -0
  330. package/dist/ui/assets/index-B61V60w_.js +19 -0
  331. package/dist/ui/assets/index-bkyxJG8J.css +1 -0
  332. package/dist/ui/assets/theme-BNq4FkXS.js +1 -0
  333. package/dist/ui/index.html +52 -0
  334. package/dist/webui.js +21 -0
  335. package/docs/backend.md +260 -0
  336. package/docs/frontend.md +125 -0
  337. package/docs/remix-icon-license.txt +186 -0
  338. package/docs/usage.md +227 -0
  339. package/package.json +82 -0
@@ -0,0 +1,667 @@
1
+ import fs from "node:fs";
2
+ import { isDeepStrictEqual } from "node:util";
3
+ import YAML from "yaml";
4
+ import { commitManagedStateTransaction, defaultManagedStateFileOperations, } from "./managed-state-transaction.js";
5
+ import { buildDefaultConfig, fetchSubscriptionProfile, isValidMihomoConfig, renderConfig, } from "./mihomo-config.js";
6
+ import { allocateProfileId, findProfileByUrl, getActiveProfile, loadProfiles, MAX_PROFILE_INTERVAL_HOURS, profileDueForUpdate, profileFilePath, profileNameFromUrl, readProfileDigest, readProfileSource, } from "./profiles.js";
7
+ import { sameSettings } from "./settings.js";
8
+ export class ProfileInputError extends Error {
9
+ }
10
+ export class GeneratedConfigError extends Error {
11
+ }
12
+ export class ProfileNotFoundError extends Error {
13
+ }
14
+ export class ProfileConflictError extends Error {
15
+ }
16
+ async function mapConcurrent(items, limit, worker) {
17
+ const results = new Array(items.length);
18
+ let cursor = 0;
19
+ const runners = Array.from({ length: Math.min(limit, items.length) }, async () => {
20
+ for (;;) {
21
+ const index = cursor++;
22
+ const item = items[index];
23
+ if (item === undefined)
24
+ return;
25
+ results[index] = await worker(item);
26
+ }
27
+ });
28
+ await Promise.all(runners);
29
+ return results;
30
+ }
31
+ function sameProfileState(before, current) {
32
+ return isDeepStrictEqual(before, current);
33
+ }
34
+ function opaqueToken() {
35
+ return Object.freeze({});
36
+ }
37
+ function replaceProfileMeta(index, replacement) {
38
+ return {
39
+ ...index,
40
+ profiles: index.profiles.map((profile) => profile.id === replacement.id ? replacement : profile),
41
+ };
42
+ }
43
+ function currentProfile(index, snapshot, activeId) {
44
+ const profile = index.profiles.find((item) => item.id === snapshot.id);
45
+ if (!profile || !isDeepStrictEqual(profile, snapshot) || index.activeId !== activeId) {
46
+ throw new ProfileConflictError(`profile changed or was removed during update: ${snapshot.id}`);
47
+ }
48
+ return profile;
49
+ }
50
+ function validFetchedInterval(value, fallback) {
51
+ return value !== undefined &&
52
+ Number.isSafeInteger(value) &&
53
+ value > 0 &&
54
+ value <= MAX_PROFILE_INTERVAL_HOURS
55
+ ? value
56
+ : fallback;
57
+ }
58
+ function withFetchedContent(profile, fetched) {
59
+ return {
60
+ ...profile,
61
+ updatedAt: new Date().toISOString(),
62
+ ...(fetched.subInfo ? { subInfo: fetched.subInfo } : {}),
63
+ ...(fetched.homePage ? { homePage: fetched.homePage } : {}),
64
+ intervalHours: validFetchedInterval(fetched.intervalHours, profile.intervalHours),
65
+ lastError: undefined,
66
+ };
67
+ }
68
+ export class ProfileService {
69
+ layout;
70
+ getSettings;
71
+ fetchProfileFn;
72
+ validateConfig;
73
+ reloadConfig;
74
+ commitBoundary;
75
+ onChange;
76
+ files;
77
+ fetches = new Map();
78
+ preparedActiveConfigs = new WeakMap();
79
+ preparedActiveReloads = new WeakMap();
80
+ commitTail = Promise.resolve();
81
+ constructor(opts) {
82
+ this.layout = opts.layout;
83
+ this.getSettings = opts.settings;
84
+ this.fetchProfileFn = opts.fetchProfile ?? fetchSubscriptionProfile;
85
+ this.validateConfig = opts.validateConfig;
86
+ this.reloadConfig = opts.reloadConfig;
87
+ this.commitBoundary = opts.commit;
88
+ this.onChange = opts.onChange;
89
+ this.files = opts.fileOperations ?? defaultManagedStateFileOperations;
90
+ }
91
+ list() {
92
+ return loadProfiles(this.layout);
93
+ }
94
+ active() {
95
+ return getActiveProfile(this.list());
96
+ }
97
+ commit(purpose, action) {
98
+ if (this.commitBoundary)
99
+ return this.commitBoundary(purpose, action);
100
+ const next = this.commitTail.then(action, action);
101
+ this.commitTail = next.then(() => undefined, () => undefined);
102
+ return next;
103
+ }
104
+ fetch(url) {
105
+ const existing = this.fetches.get(url);
106
+ if (existing)
107
+ return existing;
108
+ const pending = this.fetchProfileFn(url).finally(() => {
109
+ if (this.fetches.get(url) === pending)
110
+ this.fetches.delete(url);
111
+ });
112
+ this.fetches.set(url, pending);
113
+ return pending;
114
+ }
115
+ settingsSnapshot() {
116
+ return { ...this.getSettings() };
117
+ }
118
+ assertSettingsUnchanged(snapshot) {
119
+ if (!sameSettings(this.getSettings(), snapshot)) {
120
+ throw new ProfileConflictError("Settings changed while preparing profile configuration");
121
+ }
122
+ }
123
+ assertProfileContentCurrent(id, digest, message = "Profile content changed while preparing configuration") {
124
+ if (readProfileDigest(this.layout, id) !== digest) {
125
+ throw new ProfileConflictError(`${message}: ${id}`);
126
+ }
127
+ }
128
+ updateSnapshot(profile, activeId, contentDigest = readProfileDigest(this.layout, profile.id)) {
129
+ return { profile, activeId, contentDigest };
130
+ }
131
+ recheckProfileSnapshot(index, snapshot, contentMessage = "Profile content changed while preparing configuration") {
132
+ const profile = currentProfile(index, snapshot.profile, snapshot.activeId);
133
+ this.assertProfileContentCurrent(profile.id, snapshot.contentDigest, contentMessage);
134
+ return profile;
135
+ }
136
+ async resolveActiveSource(index) {
137
+ const active = getActiveProfile(index);
138
+ if (!active) {
139
+ return {
140
+ activeId: index.activeId,
141
+ active: null,
142
+ sourceDigest: null,
143
+ doc: null,
144
+ };
145
+ }
146
+ const stored = readProfileSource(this.layout, active.id);
147
+ const fetched = stored === undefined && active.url ? await this.fetch(active.url) : undefined;
148
+ if (stored === undefined && !fetched) {
149
+ throw new ProfileInputError(`Local profile file is missing: ${active.id}`);
150
+ }
151
+ return {
152
+ activeId: index.activeId,
153
+ active,
154
+ sourceDigest: stored?.digest ?? null,
155
+ doc: fetched?.doc ?? stored?.doc ?? null,
156
+ ...(fetched ? { fetched } : {}),
157
+ };
158
+ }
159
+ takePreparedActiveConfig(prepared) {
160
+ const state = this.preparedActiveConfigs.get(prepared);
161
+ if (!state) {
162
+ throw new TypeError("Prepared active configuration is invalid or already consumed");
163
+ }
164
+ this.preparedActiveConfigs.delete(prepared);
165
+ return state;
166
+ }
167
+ takePreparedActiveReload(prepared) {
168
+ const state = this.preparedActiveReloads.get(prepared);
169
+ if (!state) {
170
+ throw new TypeError("Prepared active reload is invalid or already consumed");
171
+ }
172
+ this.preparedActiveReloads.delete(prepared);
173
+ return state;
174
+ }
175
+ preparedActiveReload(state) {
176
+ const prepared = opaqueToken();
177
+ this.preparedActiveReloads.set(prepared, state);
178
+ return prepared;
179
+ }
180
+ async prepareActiveConfig(settings, rollbackSettings = this.settingsSnapshot()) {
181
+ const rollbackSnapshot = { ...rollbackSettings };
182
+ const source = await this.resolveActiveSource(this.list());
183
+ const generated = await this.prepare(source.doc, settings);
184
+ let rollbackConfig;
185
+ if (sameSettings(settings, rollbackSnapshot)) {
186
+ rollbackConfig = { generated };
187
+ }
188
+ else {
189
+ try {
190
+ rollbackConfig = { generated: await this.prepare(source.doc, rollbackSnapshot) };
191
+ }
192
+ catch (error) {
193
+ rollbackConfig = { error };
194
+ }
195
+ }
196
+ const prepared = opaqueToken();
197
+ this.preparedActiveConfigs.set(prepared, {
198
+ generated,
199
+ rollbackConfig,
200
+ rollbackSettings: rollbackSnapshot,
201
+ activeId: source.activeId,
202
+ sourceDigest: source.sourceDigest,
203
+ ...(source.active ? { active: { id: source.active.id, url: source.active.url } } : {}),
204
+ ...(source.fetched ? { fetched: source.fetched } : {}),
205
+ });
206
+ return prepared;
207
+ }
208
+ async withPreparedActivePublication(prepared, callback) {
209
+ const state = this.takePreparedActiveConfig(prepared);
210
+ this.assertSettingsUnchanged(state.rollbackSettings);
211
+ const index = this.list();
212
+ if (index.activeId !== state.activeId) {
213
+ throw new ProfileConflictError("Active profile changed while preparing configuration");
214
+ }
215
+ let active = null;
216
+ if (state.active) {
217
+ active = getActiveProfile(index);
218
+ if (!active || active.id !== state.active.id || active.url !== state.active.url) {
219
+ throw new ProfileConflictError("Active profile changed while preparing configuration");
220
+ }
221
+ this.assertProfileContentCurrent(active.id, state.sourceDigest);
222
+ }
223
+ const rollback = this.preparedActiveReload({
224
+ ...state.rollbackConfig,
225
+ settings: state.rollbackSettings,
226
+ activeId: index.activeId,
227
+ sourceDigest: state.sourceDigest,
228
+ ...(active ? { active } : {}),
229
+ });
230
+ const publication = active && state.fetched
231
+ ? {
232
+ config: state.generated,
233
+ rollback,
234
+ index: replaceProfileMeta(index, withFetchedContent(active, state.fetched)),
235
+ profile: { id: active.id, yamlText: state.fetched.yamlText },
236
+ }
237
+ : { config: state.generated, rollback };
238
+ const result = await callback(publication);
239
+ if (publication.profile)
240
+ this.notifyChange();
241
+ return result;
242
+ }
243
+ async prepareActiveReload() {
244
+ const source = await this.resolveActiveSource(this.list());
245
+ const settings = this.settingsSnapshot();
246
+ return this.preparedActiveReload({
247
+ generated: await this.prepare(source.doc, settings),
248
+ settings,
249
+ activeId: source.activeId,
250
+ sourceDigest: source.sourceDigest,
251
+ ...(source.active ? { active: source.active } : {}),
252
+ ...(source.fetched ? { fetched: source.fetched } : {}),
253
+ });
254
+ }
255
+ async withPreparedActiveReloadPublication(prepared, callback) {
256
+ const state = this.takePreparedActiveReload(prepared);
257
+ if ("error" in state)
258
+ throw state.error;
259
+ this.assertSettingsUnchanged(state.settings);
260
+ const index = this.list();
261
+ if (index.activeId !== state.activeId) {
262
+ throw new ProfileConflictError("Active profile changed while preparing configuration");
263
+ }
264
+ let publication;
265
+ if (!state.active) {
266
+ publication = { config: state.generated, index };
267
+ }
268
+ else {
269
+ const profile = this.recheckProfileSnapshot(index, this.updateSnapshot(state.active, state.activeId, state.sourceDigest));
270
+ const updated = state.fetched ? withFetchedContent(profile, state.fetched) : profile;
271
+ publication = {
272
+ config: state.generated,
273
+ index: replaceProfileMeta(index, updated),
274
+ ...(state.fetched ? { profile: { id: profile.id, yamlText: state.fetched.yamlText } } : {}),
275
+ };
276
+ }
277
+ const result = await callback(publication);
278
+ this.notifyChange();
279
+ return result;
280
+ }
281
+ async commitPreparedActiveReload(prepared, options = {}) {
282
+ const reloadRuntime = options.reloadRuntime ?? true;
283
+ const boundary = options.boundary ?? "acquire";
284
+ if (boundary !== "acquire" && boundary !== "already-held") {
285
+ throw new TypeError(`Unknown profile commit boundary: ${String(boundary)}`);
286
+ }
287
+ const action = () => this.withPreparedActiveReloadPublication(prepared, async (publication) => {
288
+ await this.publishTransaction(publication.index, {
289
+ ...(publication.profile ? { profile: publication.profile } : {}),
290
+ config: publication.config,
291
+ reloadRuntime,
292
+ });
293
+ return publication.config;
294
+ });
295
+ return boundary === "acquire" ? this.commit("reload active profile", action) : action();
296
+ }
297
+ async reloadActive(options = {}) {
298
+ return this.commitPreparedActiveReload(await this.prepareActiveReload(), options);
299
+ }
300
+ notifyChange() {
301
+ try {
302
+ this.onChange?.();
303
+ }
304
+ catch {
305
+ }
306
+ }
307
+ async prepare(doc, settings) {
308
+ const generated = renderConfig(doc ?? buildDefaultConfig(), settings, doc ? "subscription" : "default");
309
+ try {
310
+ await this.validateConfig?.(generated);
311
+ }
312
+ catch (err) {
313
+ throw new GeneratedConfigError(err.message);
314
+ }
315
+ return generated;
316
+ }
317
+ async publishTransaction(index, opts = {}) {
318
+ await commitManagedStateTransaction(this.layout, { index, ...opts }, this.reloadConfig, this.files);
319
+ }
320
+ async publish(index, opts = {}) {
321
+ await this.publishTransaction(index, opts);
322
+ this.notifyChange();
323
+ }
324
+ async addRemote(url, opts = {}) {
325
+ const normalizedUrl = url.trim();
326
+ if (!normalizedUrl)
327
+ throw new ProfileInputError("Missing required profile URL");
328
+ const before = this.list();
329
+ const settings = this.settingsSnapshot();
330
+ const known = findProfileByUrl(before, normalizedUrl);
331
+ const knownDigest = known ? readProfileDigest(this.layout, known.id) : null;
332
+ const fetched = await this.fetch(normalizedUrl);
333
+ const prepared = await this.prepare(fetched.doc, settings);
334
+ return this.commit("add profile", async () => {
335
+ this.assertSettingsUnchanged(settings);
336
+ const current = this.list();
337
+ let profile;
338
+ let profileChange;
339
+ if (known) {
340
+ const existing = currentProfile(current, known, before.activeId);
341
+ this.assertProfileContentCurrent(existing.id, knownDigest, "Profile changed while updating");
342
+ profile = withFetchedContent(existing, fetched);
343
+ profileChange = { id: profile.id, yamlText: fetched.yamlText };
344
+ }
345
+ else {
346
+ if (!sameProfileState(before, current) || findProfileByUrl(current, normalizedUrl)) {
347
+ throw new ProfileConflictError("Profiles changed while adding a remote profile");
348
+ }
349
+ const id = allocateProfileId(current, this.layout);
350
+ const now = new Date().toISOString();
351
+ profile = {
352
+ id,
353
+ name: opts.name?.trim() || fetched.name || profileNameFromUrl(normalizedUrl),
354
+ url: normalizedUrl,
355
+ intervalHours: validFetchedInterval(fetched.intervalHours, 24),
356
+ createdAt: now,
357
+ updatedAt: now,
358
+ ...(fetched.subInfo ? { subInfo: fetched.subInfo } : {}),
359
+ ...(fetched.homePage ? { homePage: fetched.homePage } : {}),
360
+ };
361
+ profileChange = { id, yamlText: fetched.yamlText };
362
+ }
363
+ const shouldActivate = opts.activate === true || current.activeId === null;
364
+ const index = {
365
+ activeId: shouldActivate ? profile.id : current.activeId,
366
+ profiles: known
367
+ ? current.profiles.map((item) => (item.id === profile.id ? profile : item))
368
+ : [...current.profiles, profile],
369
+ };
370
+ await this.publish(index, {
371
+ profile: profileChange,
372
+ ...(shouldActivate ? { config: prepared } : {}),
373
+ });
374
+ return {
375
+ profile,
376
+ activated: shouldActivate,
377
+ ...(shouldActivate ? { proxyCount: prepared.proxyCount } : {}),
378
+ };
379
+ });
380
+ }
381
+ async importLocal(name, content) {
382
+ if (!content.trim())
383
+ throw new ProfileInputError("Missing required profile content");
384
+ let doc;
385
+ try {
386
+ doc = YAML.parse(content);
387
+ }
388
+ catch (err) {
389
+ throw new ProfileInputError(`Content is not valid YAML: ${err.message}`);
390
+ }
391
+ if (!isValidMihomoConfig(doc)) {
392
+ throw new ProfileInputError("Content is not a valid core configuration (missing proxies/rules)");
393
+ }
394
+ const settings = this.settingsSnapshot();
395
+ const prepared = await this.prepare(doc, settings);
396
+ const before = this.list();
397
+ return this.commit("import profile", async () => {
398
+ this.assertSettingsUnchanged(settings);
399
+ const current = this.list();
400
+ if (!sameProfileState(before, current)) {
401
+ throw new ProfileConflictError("Profiles changed while importing a local profile");
402
+ }
403
+ const id = allocateProfileId(current, this.layout);
404
+ const now = new Date().toISOString();
405
+ const profile = {
406
+ id,
407
+ name: name.trim() || "imported",
408
+ url: "",
409
+ intervalHours: 0,
410
+ createdAt: now,
411
+ updatedAt: now,
412
+ };
413
+ const activated = current.activeId === null;
414
+ await this.publish({ activeId: activated ? id : current.activeId, profiles: [...current.profiles, profile] }, {
415
+ profile: { id, yamlText: content },
416
+ ...(activated ? { config: prepared } : {}),
417
+ });
418
+ return { profile, activated, ...(activated ? { proxyCount: prepared.proxyCount } : {}) };
419
+ });
420
+ }
421
+ readContent(id) {
422
+ const profile = this.list().profiles.find((item) => item.id === id);
423
+ if (!profile)
424
+ throw new ProfileNotFoundError(`profile not found: ${id}`);
425
+ let content;
426
+ try {
427
+ content = fs.readFileSync(profileFilePath(this.layout, profile.id), "utf8");
428
+ }
429
+ catch {
430
+ throw new ProfileNotFoundError(`profile file is missing: ${profile.id}`);
431
+ }
432
+ return { name: profile.name, content };
433
+ }
434
+ async writeContent(id, content) {
435
+ if (!content.trim())
436
+ throw new ProfileInputError("Missing required profile content");
437
+ let doc;
438
+ try {
439
+ doc = YAML.parse(content);
440
+ }
441
+ catch (err) {
442
+ throw new ProfileInputError(`Content is not valid YAML: ${err.message}`);
443
+ }
444
+ if (!isValidMihomoConfig(doc)) {
445
+ throw new ProfileInputError("Content is not a valid core configuration (missing proxies/rules)");
446
+ }
447
+ const before = this.list();
448
+ const target = before.profiles.find((item) => item.id === id);
449
+ if (!target)
450
+ throw new ProfileNotFoundError(`profile not found: ${id}`);
451
+ const snapshot = this.updateSnapshot(target, before.activeId);
452
+ const settings = this.settingsSnapshot();
453
+ const prepared = await this.prepare(doc, settings);
454
+ return this.commit("edit profile", async () => {
455
+ this.assertSettingsUnchanged(settings);
456
+ const index = this.list();
457
+ const profile = this.recheckProfileSnapshot(index, snapshot, "Profile changed while editing");
458
+ const updated = {
459
+ ...profile,
460
+ updatedAt: new Date().toISOString(),
461
+ lastError: undefined,
462
+ };
463
+ const active = index.activeId === profile.id;
464
+ await this.publish({
465
+ ...index,
466
+ profiles: index.profiles.map((item) => (item.id === profile.id ? updated : item)),
467
+ }, {
468
+ profile: { id: profile.id, yamlText: content },
469
+ ...(active ? { config: prepared } : {}),
470
+ });
471
+ return { profile: updated, ...(active ? { proxyCount: prepared.proxyCount } : {}) };
472
+ });
473
+ }
474
+ async activate(id) {
475
+ const before = this.list();
476
+ const settings = this.settingsSnapshot();
477
+ if (id === null) {
478
+ const prepared = await this.prepare(null, settings);
479
+ return this.commit("deselect profile", async () => {
480
+ this.assertSettingsUnchanged(settings);
481
+ const current = this.list();
482
+ if (current.activeId !== before.activeId) {
483
+ throw new ProfileConflictError("Active profile changed while preparing configuration");
484
+ }
485
+ await this.publish({ ...current, activeId: null }, { config: prepared });
486
+ return { activeId: null, proxyCount: prepared.proxyCount };
487
+ });
488
+ }
489
+ const initial = before.profiles.find((profile) => profile.id === id);
490
+ if (!initial)
491
+ throw new ProfileNotFoundError(`profile not found: ${id}`);
492
+ const stored = readProfileSource(this.layout, id);
493
+ const fetched = stored === undefined && initial.url ? await this.fetch(initial.url) : undefined;
494
+ if (stored === undefined && !fetched) {
495
+ throw new ProfileInputError(`Local profile file is missing: ${id}`);
496
+ }
497
+ const sourceDigest = stored?.digest ?? null;
498
+ const prepared = await this.prepare(fetched?.doc ?? stored?.doc ?? null, settings);
499
+ return this.commit("activate profile", async () => {
500
+ this.assertSettingsUnchanged(settings);
501
+ const current = this.list();
502
+ const profile = currentProfile(current, initial, before.activeId);
503
+ this.assertProfileContentCurrent(profile.id, sourceDigest);
504
+ const updated = fetched ? withFetchedContent(profile, fetched) : profile;
505
+ await this.publish({
506
+ activeId: id,
507
+ profiles: current.profiles.map((item) => (item.id === id ? updated : item)),
508
+ }, {
509
+ ...(fetched ? { profile: { id, yamlText: fetched.yamlText } } : {}),
510
+ config: prepared,
511
+ });
512
+ return { activeId: id, proxyCount: prepared.proxyCount };
513
+ });
514
+ }
515
+ async commitFetched(snapshot, fetched) {
516
+ const settings = this.settingsSnapshot();
517
+ const prepared = await this.prepare(fetched.doc, settings);
518
+ return this.commit("update profile", async () => {
519
+ this.assertSettingsUnchanged(settings);
520
+ const index = this.list();
521
+ const current = currentProfile(index, snapshot.profile, snapshot.activeId);
522
+ this.assertProfileContentCurrent(current.id, snapshot.contentDigest, "Profile changed while updating");
523
+ const profile = withFetchedContent(current, fetched);
524
+ const active = index.activeId === current.id;
525
+ await this.publish({
526
+ ...index,
527
+ profiles: index.profiles.map((item) => (item.id === current.id ? profile : item)),
528
+ }, {
529
+ profile: { id: current.id, yamlText: fetched.yamlText },
530
+ ...(active ? { config: prepared } : {}),
531
+ });
532
+ return { profile, ...(active ? { proxyCount: prepared.proxyCount } : {}) };
533
+ });
534
+ }
535
+ async recordError(snapshot, error) {
536
+ await this.commit("record profile update error", async () => {
537
+ const index = this.list();
538
+ let current;
539
+ try {
540
+ current = currentProfile(index, snapshot.profile, snapshot.activeId);
541
+ this.assertProfileContentCurrent(current.id, snapshot.contentDigest, "Profile changed while recording an update error");
542
+ }
543
+ catch (err) {
544
+ if (err instanceof ProfileConflictError)
545
+ return;
546
+ throw err;
547
+ }
548
+ const profile = { ...current, lastError: error.slice(0, 300) };
549
+ await this.publish({
550
+ ...index,
551
+ profiles: index.profiles.map((item) => (item.id === current.id ? profile : item)),
552
+ });
553
+ });
554
+ }
555
+ async update(id) {
556
+ const index = this.list();
557
+ const profile = index.profiles.find((item) => item.id === id);
558
+ if (!profile)
559
+ throw new ProfileNotFoundError(`profile not found: ${id}`);
560
+ if (!profile.url)
561
+ throw new ProfileInputError("Local profile has no URL to update from");
562
+ const snapshot = this.updateSnapshot(profile, index.activeId);
563
+ try {
564
+ return await this.commitFetched(snapshot, await this.fetch(profile.url));
565
+ }
566
+ catch (err) {
567
+ await this.recordError(snapshot, err.message);
568
+ throw err;
569
+ }
570
+ }
571
+ async updateProfiles(profiles) {
572
+ const fetched = await mapConcurrent(profiles, 4, async (snapshot) => {
573
+ try {
574
+ return { snapshot, fetched: await this.fetch(snapshot.profile.url) };
575
+ }
576
+ catch (err) {
577
+ return { snapshot, error: err.message };
578
+ }
579
+ });
580
+ let updated = 0;
581
+ let proxyCount;
582
+ const failed = [];
583
+ for (const result of fetched) {
584
+ const { profile } = result.snapshot;
585
+ if (!("fetched" in result)) {
586
+ await this.recordError(result.snapshot, result.error);
587
+ failed.push({ id: profile.id, name: profile.name, error: result.error });
588
+ continue;
589
+ }
590
+ try {
591
+ const committed = await this.commitFetched(result.snapshot, result.fetched);
592
+ updated += 1;
593
+ if (committed.proxyCount !== undefined)
594
+ proxyCount = committed.proxyCount;
595
+ }
596
+ catch (err) {
597
+ const message = err.message;
598
+ await this.recordError(result.snapshot, message);
599
+ failed.push({ id: profile.id, name: profile.name, error: message });
600
+ }
601
+ }
602
+ return { updated, failed, ...(proxyCount !== undefined ? { proxyCount } : {}) };
603
+ }
604
+ async updateAll() {
605
+ const index = this.list();
606
+ return this.updateProfiles(index.profiles
607
+ .filter((profile) => profile.url !== "")
608
+ .map((profile) => this.updateSnapshot(profile, index.activeId)));
609
+ }
610
+ async updateDue(nowMs = Date.now()) {
611
+ const index = this.list();
612
+ return this.updateProfiles(index.profiles
613
+ .filter((profile) => {
614
+ let exists = false;
615
+ try {
616
+ exists = fs.existsSync(profileFilePath(this.layout, profile.id));
617
+ }
618
+ catch {
619
+ }
620
+ return profileDueForUpdate(profile, exists, nowMs);
621
+ })
622
+ .map((profile) => this.updateSnapshot(profile, index.activeId)));
623
+ }
624
+ async remove(id) {
625
+ const before = this.list();
626
+ const initial = before.profiles.find((profile) => profile.id === id);
627
+ if (!initial)
628
+ throw new ProfileNotFoundError(`profile not found: ${id}`);
629
+ const sourceDigest = readProfileDigest(this.layout, id);
630
+ const settings = this.settingsSnapshot();
631
+ const prepared = before.activeId === id ? await this.prepare(null, settings) : undefined;
632
+ return this.commit("remove profile", async () => {
633
+ this.assertSettingsUnchanged(settings);
634
+ const index = this.list();
635
+ const profile = currentProfile(index, initial, before.activeId);
636
+ this.assertProfileContentCurrent(profile.id, sourceDigest, "Profile changed while deleting");
637
+ const wasActive = index.activeId === profile.id;
638
+ await this.publish({
639
+ activeId: wasActive ? null : index.activeId,
640
+ profiles: index.profiles.filter((item) => item.id !== id),
641
+ }, {
642
+ profile: { id, yamlText: null },
643
+ ...(prepared ? { config: prepared } : {}),
644
+ });
645
+ return { wasActive, ...(prepared ? { proxyCount: prepared.proxyCount } : {}) };
646
+ });
647
+ }
648
+ async rename(id, name) {
649
+ const trimmed = name.trim();
650
+ if (!trimmed)
651
+ throw new ProfileInputError("Missing required profile name");
652
+ if (trimmed.length > 120)
653
+ throw new ProfileInputError("Profile name is too long");
654
+ return this.commit("rename profile", async () => {
655
+ const index = this.list();
656
+ const profile = index.profiles.find((item) => item.id === id);
657
+ if (!profile)
658
+ throw new ProfileNotFoundError(`profile not found: ${id}`);
659
+ const updated = { ...profile, name: trimmed };
660
+ await this.publish({
661
+ ...index,
662
+ profiles: index.profiles.map((item) => (item.id === id ? updated : item)),
663
+ });
664
+ return { profile: updated };
665
+ });
666
+ }
667
+ }