@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.
- package/CHANGELOG.md +184 -0
- package/LICENSE +21 -0
- package/README.md +71 -0
- package/THIRD_PARTY_NOTICES.md +54 -0
- package/dist/api.js +103 -0
- package/dist/browser.js +25 -0
- package/dist/cli-errors.js +15 -0
- package/dist/cli.js +122 -0
- package/dist/commands/lifecycle.js +50 -0
- package/dist/commands/logs.js +54 -0
- package/dist/commands/shared.js +8 -0
- package/dist/commands/status.js +39 -0
- package/dist/commands/update.js +5 -0
- package/dist/commands/upgrade.js +43 -0
- package/dist/commands/web.js +33 -0
- package/dist/contracts.js +412 -0
- package/dist/core-config-validation.js +72 -0
- package/dist/core-install-record.js +55 -0
- package/dist/core-install-transaction.js +114 -0
- package/dist/core-update-coordination.js +105 -0
- package/dist/core-update-service.js +245 -0
- package/dist/core-update.js +804 -0
- package/dist/core-version.js +18 -0
- package/dist/core.js +206 -0
- package/dist/daemon/app.js +157 -0
- package/dist/daemon/context.js +40 -0
- package/dist/daemon/entry.js +67 -0
- package/dist/daemon/errors.js +47 -0
- package/dist/daemon/handlers/core.js +17 -0
- package/dist/daemon/handlers/daemon.js +66 -0
- package/dist/daemon/handlers/profiles.js +57 -0
- package/dist/daemon/handlers/proxy.js +12 -0
- package/dist/daemon/handlers/settings.js +48 -0
- package/dist/daemon/router.js +312 -0
- package/dist/daemon/scheduler.js +33 -0
- package/dist/daemon/server.js +121 -0
- package/dist/daemon-auth.js +71 -0
- package/dist/daemon-client.js +55 -0
- package/dist/daemon-entry.js +13 -0
- package/dist/daemon-http.js +93 -0
- package/dist/daemon-lifecycle.js +378 -0
- package/dist/daemon-proxy.js +176 -0
- package/dist/daemon-static.js +106 -0
- package/dist/daemon.js +3 -0
- package/dist/error-utils.js +14 -0
- package/dist/fs-atomic.js +123 -0
- package/dist/github.js +187 -0
- package/dist/http.js +282 -0
- package/dist/json-shape.js +17 -0
- package/dist/log-follow.js +280 -0
- package/dist/log.js +17 -0
- package/dist/managed-state-transaction.js +377 -0
- package/dist/mihomo-config.js +334 -0
- package/dist/node-version-guard.js +8 -0
- package/dist/offline-mutation.js +58 -0
- package/dist/paths.js +57 -0
- package/dist/process.js +572 -0
- package/dist/profile-migration.js +101 -0
- package/dist/profile-service.js +667 -0
- package/dist/profiles.js +182 -0
- package/dist/runtime-lifecycle.js +205 -0
- package/dist/runtime-owner.js +87 -0
- package/dist/runtime-recovery.js +31 -0
- package/dist/sash-client.js +163 -0
- package/dist/settings-service.js +242 -0
- package/dist/settings.js +314 -0
- package/dist/state-lock.js +331 -0
- package/dist/status.js +279 -0
- package/dist/supervisor.js +301 -0
- package/dist/sysproxy/common.js +99 -0
- package/dist/sysproxy/darwin.js +287 -0
- package/dist/sysproxy/factory.js +71 -0
- package/dist/sysproxy/gnome.js +181 -0
- package/dist/sysproxy/legacy.js +58 -0
- package/dist/sysproxy/snapshot.js +284 -0
- package/dist/sysproxy/types.js +24 -0
- package/dist/sysproxy/windows.js +216 -0
- package/dist/sysproxy.js +8 -0
- package/dist/system-proxy-manager.js +460 -0
- package/dist/tun-guidance.js +11 -0
- package/dist/ui/assets/05129d5ad91a6181d272cc3d031363d6-DJkDXMrK.woff2 +0 -0
- package/dist/ui/assets/0526ebf94418638dfd5b6cd9b06c07d4-BzWe4Lji.woff2 +0 -0
- package/dist/ui/assets/053f2647a78f659f6f8a3ae8436bd265-BocXYDl5.woff2 +0 -0
- package/dist/ui/assets/078f666863424d3fd2d98e3ea528704c-DQ_QCvOt.woff2 +0 -0
- package/dist/ui/assets/07f3ff379663ffa0222c12f241421dd9-Cma7oDlr.woff2 +0 -0
- package/dist/ui/assets/08427b8d865853bbd7c439321b26495f-CJmC8fWx.woff2 +0 -0
- package/dist/ui/assets/095224e4db29d40c54098da02b266b0a-DjsP0nQn.woff2 +0 -0
- package/dist/ui/assets/0ada18ca45940b08accde5c61ee871e3-E1BZD3RX.woff2 +0 -0
- package/dist/ui/assets/0be242294f7d791af850c6df38ac78a0-2cL6Ntwf.woff2 +0 -0
- package/dist/ui/assets/0bf18c938d0c27cce06c39f855405fdb-CruLZZZJ.woff2 +0 -0
- package/dist/ui/assets/0cfb3e9f4edf60839c37dc83e6f75ea3-DzAtRHvi.woff2 +0 -0
- package/dist/ui/assets/0fbbd340d0467dfe3101d58846f30cb0-gtFcO250.woff2 +0 -0
- package/dist/ui/assets/1063fa2dc7334d1f383e30824e016eae-B7U-kD-0.woff2 +0 -0
- package/dist/ui/assets/109e26e62ddc85d946e699464afbece7-BLnUjAEV.woff2 +0 -0
- package/dist/ui/assets/12a443b0ae9748a993b5395f8fad412b-Btjff2Lz.woff2 +0 -0
- package/dist/ui/assets/135c4621de7a4d85d852e03d416f13ee-DdSuW36e.woff2 +0 -0
- package/dist/ui/assets/13aa415469ee761c968749b1c0c56ec6-Dwe-Wa_w.woff2 +0 -0
- package/dist/ui/assets/1435b1ad447bd9c0e8bc52035c9dcfa8-C05ldXtw.woff2 +0 -0
- package/dist/ui/assets/175e0915aa874e7446b79b6bc9b00721-Dp0-l77L.woff2 +0 -0
- package/dist/ui/assets/1876e7d02187f006e2b7f769391c3089-CNjpRdap.woff2 +0 -0
- package/dist/ui/assets/1886dc284b3b845e3b5fe04aa2107276-Bh_wHwL1.woff2 +0 -0
- package/dist/ui/assets/19a3dda6611aaf5d787403bd1159fe15-zoEBvdpA.woff2 +0 -0
- package/dist/ui/assets/1afa74e14da193125e45d4783d358a4f-BgjSTENX.woff2 +0 -0
- package/dist/ui/assets/1cd26a6a6d784720b4a43053e9058bcf-TAP3dvNi.woff2 +0 -0
- package/dist/ui/assets/1ce19bddc3beab385a863b1e05685678-BCdXitZw.woff2 +0 -0
- package/dist/ui/assets/1d3459ac6578c80502bdb876a4824a93-CSwdnEGP.woff2 +0 -0
- package/dist/ui/assets/1d479e364dab4af01c539f0a6a7ac036-ZFI-djGt.woff2 +0 -0
- package/dist/ui/assets/1dcea2408d17fcec4197adce50f8b32d-CnK2xXR-.woff2 +0 -0
- package/dist/ui/assets/1e855385699999e24a33f7df8aed0489-DGFHkDVQ.woff2 +0 -0
- package/dist/ui/assets/1f55944b8f7972580e3029694462a6b0-CB7ogVJ9.woff2 +0 -0
- package/dist/ui/assets/1f96eda70929089762dc3c4feb33f9c8-DtSQ6YOf.woff2 +0 -0
- package/dist/ui/assets/1fb275532caadc746455881b31da27c4-BHFTsg74.woff2 +0 -0
- package/dist/ui/assets/20e679bdc00d06f802ea71fd9154b284-D9gRjq_v.woff2 +0 -0
- package/dist/ui/assets/25aff66c4a9f8cb801cbd525f4a923e4-Dgi87hP7.woff2 +0 -0
- package/dist/ui/assets/28e6700380b023b23e77f2d75aee624f-b19D855Q.woff2 +0 -0
- package/dist/ui/assets/2a1c50aa9cd99ce3a0197f5d49948e63-DrztdXCB.woff2 +0 -0
- package/dist/ui/assets/2b4e0ae0bf3af610b97717dfd66a012d-CBoapvha.woff2 +0 -0
- package/dist/ui/assets/2be2b7a672b616bc2f07059a67bfaad8-BUR0aNML.woff2 +0 -0
- package/dist/ui/assets/2e0a358ada78ea98ec8ba86002a27eac-DvgbSr9v.woff2 +0 -0
- package/dist/ui/assets/2e1cf63a2a22a50516c49b1dc521ea8a-CmltIxig.woff2 +0 -0
- package/dist/ui/assets/2e4481801925fcb6573c4337c73f15d7-DYOoCk-m.woff2 +0 -0
- package/dist/ui/assets/2fe5e634c568cd874f94d24b8f02bfd1-DcEHY35G.woff2 +0 -0
- package/dist/ui/assets/309c33f63f29887fb557115aa24d4bb5-CUgqrXIj.woff2 +0 -0
- package/dist/ui/assets/3158f45c959e1b346b0bbd6f3193dbf0-kimcClqp.woff2 +0 -0
- package/dist/ui/assets/316adbc8037a36f8d804637b053bbe5e-CjbZmJf9.woff2 +0 -0
- package/dist/ui/assets/319e9631bc29fc7235b70fc9aac43cd9-IV6yZuHZ.woff2 +0 -0
- package/dist/ui/assets/32054f9bb88d9431b1cdf023f770e614-CYrpCgqK.woff2 +0 -0
- package/dist/ui/assets/3245fa1aac6338c1d9d04befb7c346f8-CzvPhVPA.woff2 +0 -0
- package/dist/ui/assets/326c730f98afa476057957f2743c81e2-GPHJ3Fpu.woff2 +0 -0
- package/dist/ui/assets/33885558dc783bf9bae6c5d4668fef07-D33us0yR.woff2 +0 -0
- package/dist/ui/assets/33cd11f5e61556e4618801c8231f2744-CoR_Tv3X.woff2 +0 -0
- package/dist/ui/assets/34a170749cb7e6c5851acc510882af6b-BCZwsihk.woff2 +0 -0
- package/dist/ui/assets/3538de2d387591258d7e9908aad803b9-CewOkERZ.woff2 +0 -0
- package/dist/ui/assets/38d7b029c036080067a2e94211098171-CettR5Qv.woff2 +0 -0
- package/dist/ui/assets/393f47efeefc9f9e9cca83eb10a82c69-Brhz4z1y.woff2 +0 -0
- package/dist/ui/assets/396a51f2b274d01f6ba606a94f3e5fe2-DgRk-jtI.woff2 +0 -0
- package/dist/ui/assets/3991bf955d4c925e84b4a6542760d4d5-II4HtRkQ.woff2 +0 -0
- package/dist/ui/assets/39b6cb6ae8d1ea0e9832b88d2c21ef99-CbJm-q6b.woff2 +0 -0
- package/dist/ui/assets/3a1133157fbd259f1a51aaa9f8418a26-3FmUjjvi.woff2 +0 -0
- package/dist/ui/assets/3a34cff07dca7a65ed2486be19229290-B7ml2yBN.woff2 +0 -0
- package/dist/ui/assets/3c339dd7c27e6b8a06eea431733e076f-dGQqXaEj.woff2 +0 -0
- package/dist/ui/assets/3d074ea67f60953b816ce7cf5d87a980-B5VkPOmg.woff2 +0 -0
- package/dist/ui/assets/3d38f54dcf4a9f4a50165d5849becf71-CVDtFSZn.woff2 +0 -0
- package/dist/ui/assets/3e3fc9a19a27f94e4ff1b815b55b2671-Ci736GLi.woff2 +0 -0
- package/dist/ui/assets/3e8abb1f606f29e2f0a8a8a6b702d263-C5sOSf5L.woff2 +0 -0
- package/dist/ui/assets/3f0ab4e1cd99bdbfdf9acbc8f4bcb9ce-0DcdTLxV.woff2 +0 -0
- package/dist/ui/assets/3f30c0482650345c981d66920f56ab61-CTD2CsZI.woff2 +0 -0
- package/dist/ui/assets/41a9aa12ad9a60758b304c1a998fb781-BTQUDLf1.woff2 +0 -0
- package/dist/ui/assets/424010b2bc5cee540b92b28da49b61bf-DT8upW2M.woff2 +0 -0
- package/dist/ui/assets/42fbdafff2d44b76c6665d51bb2cee9d-_UrojhXD.woff2 +0 -0
- package/dist/ui/assets/4341db13c311b3cb0eaaf6b4c71b89e5-CzjA7MzG.woff2 +0 -0
- package/dist/ui/assets/434db5a796128131fde78d4389d14b71-AeNdWKua.woff2 +0 -0
- package/dist/ui/assets/43cb51fc70a15274d49e2d956c5ec3b4-BQbufd96.woff2 +0 -0
- package/dist/ui/assets/43e79de1c7815f21efbc966ff96fcbd6-DlCQvOsQ.woff2 +0 -0
- package/dist/ui/assets/43faf6a8a3cde1088bd5f7c830890a89-SvDzF3nA.woff2 +0 -0
- package/dist/ui/assets/44731ae21d7690ce538d0f8242755833-CEmu9Z88.woff2 +0 -0
- package/dist/ui/assets/45324b2a64cc0c0db797d5c09e239d34-BuFudtvM.woff2 +0 -0
- package/dist/ui/assets/4584df05d00aa75ae6e1f850fdc9df1d-GNHt6xwM.woff2 +0 -0
- package/dist/ui/assets/48d539539e98481fb511a761031c59fd-Cd9gdi8B.woff2 +0 -0
- package/dist/ui/assets/49a1fb9fc6a35796b2e0d3539088ede8-CD1cRDOy.woff2 +0 -0
- package/dist/ui/assets/4bec02eb0c2371e59bfb7d41a141ea1e-CmBnNPo0.woff2 +0 -0
- package/dist/ui/assets/4c5a81ef5683f6fbf9935e640de804e7-B4wQxzVs.woff2 +0 -0
- package/dist/ui/assets/4f11c9c75e86214b7f69ddee3a9f8076-DHsQ2xpE.woff2 +0 -0
- package/dist/ui/assets/50434b8ff88e27101d1c67ba953ee873-PH_hIqxu.woff2 +0 -0
- package/dist/ui/assets/5107fd5153d52c6a3f8d43ff214dd6c2-ls3eo5Qd.woff2 +0 -0
- package/dist/ui/assets/520cb7d3683dee99e767650efc0aa5b1-Lkd4z-9S.woff2 +0 -0
- package/dist/ui/assets/5455087df2f091b7bf3d38c5a102a2e7-QNkiOxwe.woff2 +0 -0
- package/dist/ui/assets/556450fdf0978d329cf8277e3b71dcdb-CtypGAPw.woff2 +0 -0
- package/dist/ui/assets/5a1b5c1b71cad50c51a79a7f4825e6e6-DQA407Ec.woff2 +0 -0
- package/dist/ui/assets/5ba02e809d2ef4f7639178e7a7d654c0-Dty0omtl.woff2 +0 -0
- package/dist/ui/assets/5bd654afe48e094f95e5142e4eaadc75-D9pUq71R.woff2 +0 -0
- package/dist/ui/assets/5c4c3ca0a3f4115879a61a96d02e3efc-RcVLBj31.woff2 +0 -0
- package/dist/ui/assets/5c737d740638e8d764f12c1e5c1cd144-D2Npjq-N.woff2 +0 -0
- package/dist/ui/assets/5e78019102102aa5741aa591313fd5b5-V7Azrw-2.woff2 +0 -0
- package/dist/ui/assets/6287cacac64e1849941f7b7d7e6316c5-DzkbPOID.woff2 +0 -0
- package/dist/ui/assets/643df7194160d54edc48d16d76e5ba74-BKmmIRjw.woff2 +0 -0
- package/dist/ui/assets/65d3b214c69ff6786fd1cab06f8394b7-B0cRUJAr.woff2 +0 -0
- package/dist/ui/assets/6698103c11bfe260d44fcf00c6d8b623-DVWYOeyb.woff2 +0 -0
- package/dist/ui/assets/692e07b3a609a8f1a21c15c45631f348-DJW5LcZH.woff2 +0 -0
- package/dist/ui/assets/694e87456af181279c2bfd551aa3c6e3-Cu7kRuJB.woff2 +0 -0
- package/dist/ui/assets/6b88066bdc7a6e8bc823128dbd0f6f3b-BG38UZSq.woff2 +0 -0
- package/dist/ui/assets/6d6f36f15381826d113832bfdd41b021-Bgh7YPiq.woff2 +0 -0
- package/dist/ui/assets/6e22361162bd0a5cd189ca3c98d23d52-DqnptHqF.woff2 +0 -0
- package/dist/ui/assets/6e884dd501b8da24ecd37425a9b3d51d-BVNqra8s.woff2 +0 -0
- package/dist/ui/assets/71200dbc7497fbafa28db5cfeae74acb-B6xMqoC6.woff2 +0 -0
- package/dist/ui/assets/72a85441a9757146440e3985f3b02075-CzTSlCPZ.woff2 +0 -0
- package/dist/ui/assets/7472d7e02045dea3ff288eac850e38eb-CboJEy4X.woff2 +0 -0
- package/dist/ui/assets/795ffa953bea666763ae79ac88aa7e5a-C6UsoDmi.woff2 +0 -0
- package/dist/ui/assets/7aa8647d6c52d249169b73417aa8e444-kJejhQ0-.woff2 +0 -0
- package/dist/ui/assets/7be6b794fc233ccd8b5769496301313a-Ba2iZC0a.woff2 +0 -0
- package/dist/ui/assets/7c0748f04d52086809c9f551c5aaded7-DSzO_ybF.woff2 +0 -0
- package/dist/ui/assets/7c858bb53a7652227fab8cf9753d3e1f-Bi9fbvIF.woff2 +0 -0
- package/dist/ui/assets/7e7026091b4e6061b05963bdb3a7f944-CrmRDJpc.woff2 +0 -0
- package/dist/ui/assets/7f80369a7479d568ab8bf492000282bd-CqcI5ZOp.woff2 +0 -0
- package/dist/ui/assets/834ce2f2571aaa26e47a618c4ad55e58-DZfWJrxo.woff2 +0 -0
- package/dist/ui/assets/834faab602d9c795341bc7dfd6578fe5-B1VeBVWh.woff2 +0 -0
- package/dist/ui/assets/8513a8eea35b460a047a16a87c36f8bb-H259w_6b.woff2 +0 -0
- package/dist/ui/assets/8699e8a36465bf46671dc28ce24e188c-YlkPpQ6h.woff2 +0 -0
- package/dist/ui/assets/86ae8881eae9eac7e9c0182c8ce6c627-Ch2r3LNs.woff2 +0 -0
- package/dist/ui/assets/86b3ae1be58d76f7147793f09264451b-CpgTfcn-.woff2 +0 -0
- package/dist/ui/assets/8769f8d7d45adcfcbd3f80460d22720d-DV_yqcTo.woff2 +0 -0
- package/dist/ui/assets/87bb0ceea9f5515490c832bdf6ebf641-CJpxd8e6.woff2 +0 -0
- package/dist/ui/assets/8928b9a50b5efbaaafa039674a3c7301-C-42WRD6.woff2 +0 -0
- package/dist/ui/assets/899f2c2a36cbeac473a7caeeaabc46b1-xrIcXJXU.woff2 +0 -0
- package/dist/ui/assets/8a075d4043e6bf66740808b492186438-Dq0346Qm.woff2 +0 -0
- package/dist/ui/assets/8b86676936792154c53205ab5d90b797-BGH_CGsg.woff2 +0 -0
- package/dist/ui/assets/8c63ca479653200a0e12f428242e1171-CsBTcPwY.woff2 +0 -0
- package/dist/ui/assets/8c9c7ff8342f5fed60e73c4a7ad67325-CGSYpt52.woff2 +0 -0
- package/dist/ui/assets/8ca655fcc5d9cf33d49815e231f3f356-b8PyEeZg.woff2 +0 -0
- package/dist/ui/assets/8e96f0f9385ef826f900587d0fc18b3d-D7GIG6Uq.woff2 +0 -0
- package/dist/ui/assets/8ea917252cf2c1e664f06ee5badffeea-CDZ1AFr0.woff2 +0 -0
- package/dist/ui/assets/8f42f890fc37e292017446af85af2745-Qx6oGI0z.woff2 +0 -0
- package/dist/ui/assets/8f9589e71c53bb753749a9c412f15c01-BVR5mPJl.woff2 +0 -0
- package/dist/ui/assets/90e70b52a3cc2491bbb2b4c0caf0ae7e-BdwPu67j.woff2 +0 -0
- package/dist/ui/assets/92ac4ca42fe050c1e680c585551abd9c-CXLyuj7l.woff2 +0 -0
- package/dist/ui/assets/92c12bb794c491930fd06f3f97299807-u_0ognJ0.woff2 +0 -0
- package/dist/ui/assets/92f0b4d2bb274a801aac10fc34c1e0cc-DFHB6dyu.woff2 +0 -0
- package/dist/ui/assets/93247aebd628367f5518eafdb2770ce9-o1N4zvTO.woff2 +0 -0
- package/dist/ui/assets/940446ea160995af66a9ee223cc3d4d1-C-_7aPYO.woff2 +0 -0
- package/dist/ui/assets/94be430de618453b6ae27bd366827ba2-1YYf1n5N.woff2 +0 -0
- package/dist/ui/assets/98cd7d62095e466e55885b34f63c86f1-z33YCk0X.woff2 +0 -0
- package/dist/ui/assets/99049fd5cec5894cafe2a1e5e13d1e03-DNrg8nIk.woff2 +0 -0
- package/dist/ui/assets/99e2752b7351aaa7681b4d91db80e89a-b3hi7miY.woff2 +0 -0
- package/dist/ui/assets/9d1b670cbfd64716ac685dea26237d95-DAZ9U6es.woff2 +0 -0
- package/dist/ui/assets/CodeEditorModal-6m0TJWyF.css +1 -0
- package/dist/ui/assets/CodeEditorModal-CFEnWsyh.js +14 -0
- package/dist/ui/assets/ConnectionsView-DHo6yvU6.css +1 -0
- package/dist/ui/assets/ConnectionsView-DNGmZBSU.js +1 -0
- package/dist/ui/assets/LogsView-BoeE92VF.css +1 -0
- package/dist/ui/assets/LogsView-fSiaxQ13.js +1 -0
- package/dist/ui/assets/PaginationFooter-B3kHzRfB.js +1 -0
- package/dist/ui/assets/PaginationFooter-BNkpcNoB.css +1 -0
- package/dist/ui/assets/ProfileEditorDialog-BydoZthX.js +1 -0
- package/dist/ui/assets/ProfilesView-Btc1DOxE.js +2 -0
- package/dist/ui/assets/ProfilesView-ByqdFNHN.css +1 -0
- package/dist/ui/assets/RulesView-CHyynVqA.css +1 -0
- package/dist/ui/assets/RulesView-D9vZBiJ1.js +1 -0
- package/dist/ui/assets/SettingsFileDialog-CNFEAVs4.js +1 -0
- package/dist/ui/assets/SettingsView-BlDhZkXQ.js +2 -0
- package/dist/ui/assets/SettingsView-CngS3vBM.css +1 -0
- package/dist/ui/assets/a0f2d41645a113d821e7ff0891daad1e-XK0rDAci.woff2 +0 -0
- package/dist/ui/assets/a1d1345d8407dd6169f39c28ff6b1708-Bn7hMmv8.woff2 +0 -0
- package/dist/ui/assets/a29f51b4ebb6776e43e7492a036be28e-CrAKKz8u.woff2 +0 -0
- package/dist/ui/assets/a3252ac86b2c4e88bfb9baecabcad817-qnA_3LNE.woff2 +0 -0
- package/dist/ui/assets/a3d63a61e7eb2bb497dbc2da65bd0873-BRMJAm0m.woff2 +0 -0
- package/dist/ui/assets/a45eea933252dbe3529f30a0323c2661-D4kBOrXR.woff2 +0 -0
- package/dist/ui/assets/a4886c4ebed8c8b1f8a87b5b491a39aa-CBQdRqpF.woff2 +0 -0
- package/dist/ui/assets/a50b4ab745c7676d9d3ceb3c0a023a3d-CW990pgI.woff2 +0 -0
- package/dist/ui/assets/a5851ae4d7e58996d8bd681f80d4727c-BBytdb0t.woff2 +0 -0
- package/dist/ui/assets/a5cdaa96bf7d1db6c671432f9ab7bccb-slKJjgfX.woff2 +0 -0
- package/dist/ui/assets/a640dd2e962cf72c7fcf76234c973303-CUCWUDBY.woff2 +0 -0
- package/dist/ui/assets/a67e457bbd366aef5104ce743a2cf6d4-DkE8fLlE.woff2 +0 -0
- package/dist/ui/assets/a6c3f2257315d45aa212bd2e86a02852-CxDbXnZV.woff2 +0 -0
- package/dist/ui/assets/a73399a40a5f81fb7314b8b9a10cd221-DRAvJPKI.woff2 +0 -0
- package/dist/ui/assets/a73a64b0941803d9d807b9ec082df641-BbPpPyy5.woff2 +0 -0
- package/dist/ui/assets/a73d1a0a1dfd4f4234f7a0649ece4da4-BHeqktY9.woff2 +0 -0
- package/dist/ui/assets/a740044e77a400a450e71fcdea55523f-Co5hpBNK.woff2 +0 -0
- package/dist/ui/assets/a7669b29f404662c179c71b87e67c8d2-CVnuBgHy.woff2 +0 -0
- package/dist/ui/assets/a964a4def15811e91b05a91740a1cf37-CzcDfCRn.woff2 +0 -0
- package/dist/ui/assets/aac3f69132da164921fda75d2d90bfb7-B6rUZQ4_.woff2 +0 -0
- package/dist/ui/assets/aacd263c3bb4ba163a4d5079c159680a-DtrDLFlk.woff2 +0 -0
- package/dist/ui/assets/ac62ec881ded2cdb450e542b396e24d0-CRlVJvPa.woff2 +0 -0
- package/dist/ui/assets/aebcb9b7d4b174ab5a4b911206f24036-BuleSpgP.woff2 +0 -0
- package/dist/ui/assets/af70712dadc05c7b18d248996a5ab0a9-gCZSZGzb.woff2 +0 -0
- package/dist/ui/assets/afbd3a8a4cfaa4561b7848c28bc6c4f8-DgnqceEw.woff2 +0 -0
- package/dist/ui/assets/b02d3867e6e2ea921d2845b44c4a5a31-C9v4FrqD.woff2 +0 -0
- package/dist/ui/assets/b1b22825b930688403337d4fd7bbba62-vmMs2pJf.woff2 +0 -0
- package/dist/ui/assets/b3206c979bfb28d12d7ad6df58e74b9f-X1PqLaCR.woff2 +0 -0
- package/dist/ui/assets/b6280e42d4f11dcab70594f9d6774936-CtezImLG.woff2 +0 -0
- package/dist/ui/assets/b71b8cd1a70405c3dbc0e7a44055ca9a-Cd6cIOEr.woff2 +0 -0
- package/dist/ui/assets/b9198c4f220a4ca075f8daa4db529e50-Bgc7zlEt.woff2 +0 -0
- package/dist/ui/assets/baad304a58d7d42d0af29235c5e9490b-BQZYwARB.woff2 +0 -0
- package/dist/ui/assets/bb99e34f8784cc44f0eeadbe735dd238-UHFYI827.woff2 +0 -0
- package/dist/ui/assets/bc1cb0f523c8a2ff566a047aeddf90fd-BSlXQn97.woff2 +0 -0
- package/dist/ui/assets/bd3171e975d62612e50148bf38b2158f-CtZKzCzQ.woff2 +0 -0
- package/dist/ui/assets/be10640c403263dd5917ad65c4d453a6-B-rbuuja.woff2 +0 -0
- package/dist/ui/assets/be764e101e78b50b749aa1414d4810a7-yIPaQQih.woff2 +0 -0
- package/dist/ui/assets/bfa682a7f5f12945b41230c35eb859f6-CzagcI5f.woff2 +0 -0
- package/dist/ui/assets/bfa9753ed26f2e1f81ab7020167bd1a0-C6ZNQQ2p.woff2 +0 -0
- package/dist/ui/assets/branding/sash-cat.png +0 -0
- package/dist/ui/assets/c0e78c6ad709b19ce437677c91033489-BtwtlApg.woff2 +0 -0
- package/dist/ui/assets/c5a59409c610cc8c265d1691eb3f71eb-DmI_ky2X.woff2 +0 -0
- package/dist/ui/assets/c5ff66a1a57f2a29424e8de5d4ef3756-C9pOsaV1.woff2 +0 -0
- package/dist/ui/assets/c607bdee84a0e5b69721367ccc0c9c31-eoHbdXeg.woff2 +0 -0
- package/dist/ui/assets/c8095033941d18db730496eb4c863757-DoexTYks.woff2 +0 -0
- package/dist/ui/assets/c8295519e16f4cf3a350e9729f07653a-C8bAtIO_.woff2 +0 -0
- package/dist/ui/assets/c91f9c9f4db81afeba37c8e73afb0bfd-BGIl2iKW.woff2 +0 -0
- package/dist/ui/assets/c9ad9caa4afdd40766add91d321b0350-HfuiRq1k.woff2 +0 -0
- package/dist/ui/assets/cc4f271c90f9ea5157db9c0a4edc976b-MAjuDvHE.woff2 +0 -0
- package/dist/ui/assets/cdf7c3d96ba172214d7e1e9fe5e6ba8b-CUx4fWQE.woff2 +0 -0
- package/dist/ui/assets/cf70d5821ea7c3ca34653169ac763606-CE0T0Svc.woff2 +0 -0
- package/dist/ui/assets/d0d5efe92496b1ceea38e180b0ac97d3-7G71Z6v3.woff2 +0 -0
- package/dist/ui/assets/d1977142eceb878b06440375e64c4372-mr-7LWm-.woff2 +0 -0
- package/dist/ui/assets/d42feb13392b4dd9214c61b61a6e5ae8-DjnKADf_.woff2 +0 -0
- package/dist/ui/assets/d46a696ec6a077c6873fc39043c3c082-C62E-13k.woff2 +0 -0
- package/dist/ui/assets/d4c0bbf1605a8a9c36fff5a2ead81bb5-C2F6tzOj.woff2 +0 -0
- package/dist/ui/assets/d918d4e47697b7c752ea223e0d0d4c81-twovU2ty.woff2 +0 -0
- package/dist/ui/assets/d988c05da0b5624267dd825fedd133d1-Bgbc9SVH.woff2 +0 -0
- package/dist/ui/assets/da3a071df79531f4f033b7c8499651d0-0tgZJL_N.woff2 +0 -0
- package/dist/ui/assets/da8eba4acbf93048645043006de1c5f0-Ce0dmHn_.woff2 +0 -0
- package/dist/ui/assets/dc3f16892e20a85a8fcad2d3097e622b-8PRrFPJt.woff2 +0 -0
- package/dist/ui/assets/dd5f808ec691f911483a2eb60b5049bd-BhVupSbT.woff2 +0 -0
- package/dist/ui/assets/e2b04534cc697a76afd70e885119cb8c-DO303iCb.woff2 +0 -0
- package/dist/ui/assets/e307280eb2fb7809adee14ff0d247d32-DHMLXvSs.woff2 +0 -0
- package/dist/ui/assets/e3b484bec850c669094f713570870b3a-BI_9kRw8.woff2 +0 -0
- package/dist/ui/assets/e7b1d196949b7530cbda0ad956fbb2cc-i2m2Id0q.woff2 +0 -0
- package/dist/ui/assets/e7c2e5fb387880508811cadc2926e13a-BfROXX4U.woff2 +0 -0
- package/dist/ui/assets/e8a5fe283c8b9e173097bee0b5757ab6-BvfjDjYP.woff2 +0 -0
- package/dist/ui/assets/e8e3465d48435612e9c3509be3984188-BeUPU5-9.woff2 +0 -0
- package/dist/ui/assets/e93903f4604b007fc1b7134a74f5c534-B1M4h46T.woff2 +0 -0
- package/dist/ui/assets/eb0994906efd969102d522b6d8b3aff2-i9SHU5CJ.woff2 +0 -0
- package/dist/ui/assets/eb32eee23ceadd3fc4eb5763d3573f57-B3h9kgf3.woff2 +0 -0
- package/dist/ui/assets/ebadbc005027fb3e2b1c2d15be38c66b-qVQSBwBu.woff2 +0 -0
- package/dist/ui/assets/ecbbb464ff6934b5012cf33c80c7a907-CBEPqZfS.woff2 +0 -0
- package/dist/ui/assets/ed79ac25c014b6434f22fe1e2549bc26-mWhz7hkU.woff2 +0 -0
- package/dist/ui/assets/eed64f40443554cb9f65edaca176abb0-Cqw29XTm.woff2 +0 -0
- package/dist/ui/assets/efc749471476cce09e12e7038c757950-BhSWPf1Q.woff2 +0 -0
- package/dist/ui/assets/f2a7c2cb375662aa93d8f31116d39c20-D1zsMYCW.woff2 +0 -0
- package/dist/ui/assets/f5ba0fb4332ccceadc481e47134023d3-CAKMPiu5.woff2 +0 -0
- package/dist/ui/assets/f6326eb5031e18478aa4588598f96965-Dj3uu55n.woff2 +0 -0
- package/dist/ui/assets/f8678cbaab9caef95e1986731dcbd92f-CnIuR3EN.woff2 +0 -0
- package/dist/ui/assets/f8d9d55e2a811e9839c9af5f8f2d62ac-DIUQRBsH.woff2 +0 -0
- package/dist/ui/assets/f98b117e363eb51f4d7c7d1056da7688-O9MtqjIW.woff2 +0 -0
- package/dist/ui/assets/f9ab5d9163148ceb211e5b4f15a3d8e0-Dtqse-AT.woff2 +0 -0
- package/dist/ui/assets/fa69ce224ff0a6c0ff97b10a829779c2-pX45dCNY.woff2 +0 -0
- package/dist/ui/assets/fc38d1da3930d39dd8e286f708105765-DmbOHwTI.woff2 +0 -0
- package/dist/ui/assets/fe03c1dfaffc1cad3521fe55f1541d6b-BW8aEqCj.woff2 +0 -0
- package/dist/ui/assets/fe0b2421eb4d1f4f8949c602871a317c-BsBLV6jH.woff2 +0 -0
- package/dist/ui/assets/fe9c75d0cb3d808023412d26dfc9d388-eXjcgUPI.woff2 +0 -0
- package/dist/ui/assets/index-B61V60w_.js +19 -0
- package/dist/ui/assets/index-bkyxJG8J.css +1 -0
- package/dist/ui/assets/theme-BNq4FkXS.js +1 -0
- package/dist/ui/index.html +52 -0
- package/dist/webui.js +21 -0
- package/docs/backend.md +260 -0
- package/docs/frontend.md +125 -0
- package/docs/remix-icon-license.txt +186 -0
- package/docs/usage.md +227 -0
- package/package.json +82 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { MihomoApi } from "./api.js";
|
|
5
|
+
import { containsCoreVersionToken } from "./core-version.js";
|
|
6
|
+
import { boundedLogTailSince, logTailCursor } from "./log-follow.js";
|
|
7
|
+
import { buildSanitizedEnv, classifyProcessIdentity, clearPidRecord, isProcessAlive, killProcessGracefully, readPidRecord, withPrivateAppendLogFds, writePidRecord, } from "./process.js";
|
|
8
|
+
function sleep(ms) {
|
|
9
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10
|
+
}
|
|
11
|
+
function managedPathsMatch(a, b) {
|
|
12
|
+
const left = path.resolve(a);
|
|
13
|
+
const right = path.resolve(b);
|
|
14
|
+
return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
15
|
+
}
|
|
16
|
+
export class CoreSupervisor {
|
|
17
|
+
child = null;
|
|
18
|
+
pidRecordOwners = new WeakSet();
|
|
19
|
+
childStartedAt;
|
|
20
|
+
childGeneration = 0;
|
|
21
|
+
stopping = false;
|
|
22
|
+
layout;
|
|
23
|
+
getSettings;
|
|
24
|
+
spawnFn;
|
|
25
|
+
waitHealthyMs;
|
|
26
|
+
getExpectedVersion;
|
|
27
|
+
isAlive;
|
|
28
|
+
kill;
|
|
29
|
+
classifyIdentity;
|
|
30
|
+
onExitCallback;
|
|
31
|
+
constructor(opts) {
|
|
32
|
+
this.layout = opts.layout;
|
|
33
|
+
this.getSettings = opts.settings;
|
|
34
|
+
this.waitHealthyMs = opts.waitHealthyMs ?? 10_000;
|
|
35
|
+
const expectedVersion = opts.expectedVersion;
|
|
36
|
+
this.getExpectedVersion =
|
|
37
|
+
typeof expectedVersion === "function" ? expectedVersion : () => expectedVersion;
|
|
38
|
+
this.isAlive = opts.isAliveFn ?? isProcessAlive;
|
|
39
|
+
this.kill = opts.killFn ?? killProcessGracefully;
|
|
40
|
+
this.classifyIdentity = opts.classifyIdentityFn ?? classifyProcessIdentity;
|
|
41
|
+
this.onExitCallback = opts.onExit;
|
|
42
|
+
this.spawnFn = opts.spawnFn ?? this.defaultSpawn.bind(this);
|
|
43
|
+
}
|
|
44
|
+
ownedChildIdentity(child) {
|
|
45
|
+
if (this.child !== child)
|
|
46
|
+
return "mismatch";
|
|
47
|
+
if (child.exitCode !== null && child.exitCode !== undefined)
|
|
48
|
+
return "mismatch";
|
|
49
|
+
if (child.signalCode !== null && child.signalCode !== undefined)
|
|
50
|
+
return "mismatch";
|
|
51
|
+
return child.pid && this.isAlive(child.pid) ? "match" : "mismatch";
|
|
52
|
+
}
|
|
53
|
+
defaultSpawn(layout, _settings) {
|
|
54
|
+
fs.mkdirSync(layout.logsDir, { recursive: true });
|
|
55
|
+
fs.mkdirSync(layout.stateDir, { recursive: true });
|
|
56
|
+
return withPrivateAppendLogFds(layout.coreLogFile, layout.coreErrLogFile, ({ stdoutFd, stderrFd }) => spawn(layout.coreExe, ["-d", layout.root, "-f", layout.configFile], {
|
|
57
|
+
cwd: layout.root,
|
|
58
|
+
stdio: ["ignore", stdoutFd, stderrFd],
|
|
59
|
+
windowsHide: true,
|
|
60
|
+
env: buildSanitizedEnv(),
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
clearOwnedPidRecord(child) {
|
|
64
|
+
if (this.pidRecordOwners.delete(child))
|
|
65
|
+
clearPidRecord(this.layout.pidFile);
|
|
66
|
+
}
|
|
67
|
+
async abortStart(child, pid, pidRecord) {
|
|
68
|
+
this.stopping = true;
|
|
69
|
+
const terminated = await this.kill(pid, {
|
|
70
|
+
timeoutMs: 3000,
|
|
71
|
+
verify: () => this.ownedChildIdentity(child),
|
|
72
|
+
});
|
|
73
|
+
if (terminated && this.child === child) {
|
|
74
|
+
this.child = null;
|
|
75
|
+
this.childStartedAt = undefined;
|
|
76
|
+
this.childGeneration++;
|
|
77
|
+
if (pidRecord === "owned")
|
|
78
|
+
this.clearOwnedPidRecord(child);
|
|
79
|
+
}
|
|
80
|
+
return terminated;
|
|
81
|
+
}
|
|
82
|
+
async probeTunActive(api) {
|
|
83
|
+
try {
|
|
84
|
+
return await api.getTunActive();
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async start() {
|
|
91
|
+
if (this.child && this.isAlive(this.child.pid ?? -1)) {
|
|
92
|
+
throw new Error(`Core is already running (PID=${this.child.pid})`);
|
|
93
|
+
}
|
|
94
|
+
if (!fs.existsSync(this.layout.coreExe)) {
|
|
95
|
+
throw new Error(`Core executable not found at ${this.layout.coreExe}`);
|
|
96
|
+
}
|
|
97
|
+
if (!fs.existsSync(this.layout.configFile)) {
|
|
98
|
+
throw new Error(`Core config not found at ${this.layout.configFile}`);
|
|
99
|
+
}
|
|
100
|
+
const expectedVersion = this.getExpectedVersion();
|
|
101
|
+
this.stopping = false;
|
|
102
|
+
const settings = this.getSettings();
|
|
103
|
+
const errLogCursor = logTailCursor(this.layout.coreErrLogFile);
|
|
104
|
+
const child = this.spawnFn(this.layout, settings);
|
|
105
|
+
let spawnError;
|
|
106
|
+
child.on("error", (err) => {
|
|
107
|
+
spawnError = err;
|
|
108
|
+
});
|
|
109
|
+
const pid = child.pid;
|
|
110
|
+
if (!pid) {
|
|
111
|
+
throw new Error("Failed to start core process (no PID returned)");
|
|
112
|
+
}
|
|
113
|
+
this.child = child;
|
|
114
|
+
this.childStartedAt = new Date().toISOString();
|
|
115
|
+
this.childGeneration++;
|
|
116
|
+
child.once("exit", (code, signal) => {
|
|
117
|
+
if (this.child !== child)
|
|
118
|
+
return;
|
|
119
|
+
const wasStopping = this.stopping;
|
|
120
|
+
this.child = null;
|
|
121
|
+
this.childStartedAt = undefined;
|
|
122
|
+
this.childGeneration++;
|
|
123
|
+
this.clearOwnedPidRecord(child);
|
|
124
|
+
if (!wasStopping) {
|
|
125
|
+
Promise.resolve(this.onExitCallback?.(code, signal)).catch(() => {
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
try {
|
|
130
|
+
writePidRecord(this.layout.pidFile, {
|
|
131
|
+
pid,
|
|
132
|
+
exe: this.layout.coreExe,
|
|
133
|
+
startedAt: this.childStartedAt,
|
|
134
|
+
});
|
|
135
|
+
this.pidRecordOwners.add(child);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
const terminated = await this.abortStart(child, pid, "uncertain");
|
|
139
|
+
const cleanup = terminated ? "" : `; process ${pid} could not be confirmed stopped`;
|
|
140
|
+
throw new Error(`Failed to persist Core PID ownership: ${err.message}${cleanup}`);
|
|
141
|
+
}
|
|
142
|
+
const api = new MihomoApi(settings.controller, settings.secret);
|
|
143
|
+
const deadline = Date.now() + this.waitHealthyMs;
|
|
144
|
+
let version;
|
|
145
|
+
let healthyProbes = 0;
|
|
146
|
+
while (Date.now() < deadline) {
|
|
147
|
+
if (spawnError) {
|
|
148
|
+
const terminated = await this.abortStart(child, pid, "owned");
|
|
149
|
+
const details = boundedLogTailSince(this.layout.coreErrLogFile, errLogCursor, {
|
|
150
|
+
maxLines: 20,
|
|
151
|
+
});
|
|
152
|
+
const cleanup = terminated ? "" : `; process ${pid} could not be confirmed stopped`;
|
|
153
|
+
throw new Error(`Failed to start core: ${spawnError.message}${cleanup}${details ? `\n${details}` : ""}`);
|
|
154
|
+
}
|
|
155
|
+
if (!this.isAlive(pid)) {
|
|
156
|
+
this.clearOwnedPidRecord(child);
|
|
157
|
+
const details = boundedLogTailSince(this.layout.coreErrLogFile, errLogCursor, {
|
|
158
|
+
maxLines: 20,
|
|
159
|
+
});
|
|
160
|
+
throw new Error(`Core exited during startup.${details ? `\nRecent errors:\n${details}` : ""}`);
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
version = await api.version();
|
|
164
|
+
if (expectedVersion && !containsCoreVersionToken(version, expectedVersion)) {
|
|
165
|
+
throw new Error(`Controller version ${version} does not match expected ${expectedVersion}`);
|
|
166
|
+
}
|
|
167
|
+
healthyProbes++;
|
|
168
|
+
if (healthyProbes >= 2 && this.isAlive(pid)) {
|
|
169
|
+
const tunActive = await this.probeTunActive(api);
|
|
170
|
+
if (this.isAlive(pid)) {
|
|
171
|
+
return {
|
|
172
|
+
pid,
|
|
173
|
+
version,
|
|
174
|
+
...(tunActive !== undefined ? { tunActive } : {}),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
healthyProbes = 0;
|
|
181
|
+
}
|
|
182
|
+
await sleep(250);
|
|
183
|
+
}
|
|
184
|
+
const terminated = await this.abortStart(child, pid, "owned");
|
|
185
|
+
const details = boundedLogTailSince(this.layout.coreErrLogFile, errLogCursor, {
|
|
186
|
+
maxLines: 20,
|
|
187
|
+
});
|
|
188
|
+
const cleanup = terminated
|
|
189
|
+
? ""
|
|
190
|
+
: " The process could not be confirmed stopped; PID state was preserved.";
|
|
191
|
+
throw new Error(`Core started (PID=${pid}) but external-controller did not become healthy within ${this.waitHealthyMs}ms.${cleanup}${details ? `\nRecent errors:\n${details}` : ""}`);
|
|
192
|
+
}
|
|
193
|
+
async stop() {
|
|
194
|
+
const child = this.child;
|
|
195
|
+
if (!child?.pid || !this.isAlive(child.pid)) {
|
|
196
|
+
if (this.child)
|
|
197
|
+
this.childGeneration++;
|
|
198
|
+
this.child = null;
|
|
199
|
+
this.childStartedAt = undefined;
|
|
200
|
+
clearPidRecord(this.layout.pidFile);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
this.stopping = true;
|
|
204
|
+
const pid = child.pid;
|
|
205
|
+
const terminated = await this.kill(pid, {
|
|
206
|
+
timeoutMs: 8000,
|
|
207
|
+
verify: () => this.ownedChildIdentity(child),
|
|
208
|
+
});
|
|
209
|
+
if (!terminated) {
|
|
210
|
+
throw new Error(`Core process is still running after termination attempt (PID=${pid})`);
|
|
211
|
+
}
|
|
212
|
+
if (this.child === child) {
|
|
213
|
+
this.child = null;
|
|
214
|
+
this.childStartedAt = undefined;
|
|
215
|
+
this.childGeneration++;
|
|
216
|
+
this.clearOwnedPidRecord(child);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async restart() {
|
|
220
|
+
await this.stop();
|
|
221
|
+
return this.start();
|
|
222
|
+
}
|
|
223
|
+
ownedCoreSnapshot() {
|
|
224
|
+
const child = this.child;
|
|
225
|
+
if (!child?.pid || !this.isAlive(child.pid))
|
|
226
|
+
return undefined;
|
|
227
|
+
return { generation: this.childGeneration, pid: child.pid };
|
|
228
|
+
}
|
|
229
|
+
ownsCore(snapshot) {
|
|
230
|
+
const child = this.child;
|
|
231
|
+
return Boolean(child &&
|
|
232
|
+
child.pid === snapshot.pid &&
|
|
233
|
+
this.childGeneration === snapshot.generation &&
|
|
234
|
+
this.isAlive(snapshot.pid));
|
|
235
|
+
}
|
|
236
|
+
async status() {
|
|
237
|
+
const ownership = this.ownedCoreSnapshot();
|
|
238
|
+
if (!ownership)
|
|
239
|
+
return { running: false };
|
|
240
|
+
const settings = this.getSettings();
|
|
241
|
+
const api = new MihomoApi(settings.controller, settings.secret);
|
|
242
|
+
let healthy = false;
|
|
243
|
+
let version;
|
|
244
|
+
let tunActive;
|
|
245
|
+
try {
|
|
246
|
+
version = await api.version();
|
|
247
|
+
healthy = true;
|
|
248
|
+
if (!this.ownsCore(ownership))
|
|
249
|
+
return { running: false };
|
|
250
|
+
tunActive = await this.probeTunActive(api);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
healthy = false;
|
|
254
|
+
}
|
|
255
|
+
if (!this.ownsCore(ownership))
|
|
256
|
+
return { running: false };
|
|
257
|
+
return {
|
|
258
|
+
running: true,
|
|
259
|
+
pid: ownership.pid,
|
|
260
|
+
startedAt: this.childStartedAt,
|
|
261
|
+
healthy,
|
|
262
|
+
version,
|
|
263
|
+
...(tunActive !== undefined ? { tunActive } : {}),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
isRunning() {
|
|
267
|
+
return Boolean(this.child && this.isAlive(this.child.pid ?? -1));
|
|
268
|
+
}
|
|
269
|
+
async cleanStaleCore() {
|
|
270
|
+
const record = readPidRecord(this.layout.pidFile);
|
|
271
|
+
if (!record) {
|
|
272
|
+
if (fs.existsSync(this.layout.pidFile)) {
|
|
273
|
+
throw new Error(`Core PID record is corrupt: ${this.layout.pidFile}`);
|
|
274
|
+
}
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (!managedPathsMatch(record.exe, this.layout.coreExe)) {
|
|
278
|
+
throw new Error(`Core PID record executable does not match the managed path: ${record.exe}`);
|
|
279
|
+
}
|
|
280
|
+
if (!this.isAlive(record.pid)) {
|
|
281
|
+
clearPidRecord(this.layout.pidFile);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const identity = this.classifyIdentity(record.pid, this.layout.coreExe);
|
|
285
|
+
if (identity === "mismatch") {
|
|
286
|
+
clearPidRecord(this.layout.pidFile);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (identity === "unknown") {
|
|
290
|
+
throw new Error(`Refusing to terminate stale Core PID ${record.pid}: process identity could not be verified`);
|
|
291
|
+
}
|
|
292
|
+
const terminated = await this.kill(record.pid, {
|
|
293
|
+
timeoutMs: 5000,
|
|
294
|
+
verify: () => this.classifyIdentity(record.pid, this.layout.coreExe),
|
|
295
|
+
});
|
|
296
|
+
if (!terminated) {
|
|
297
|
+
throw new Error(`Stale Core process is still running after termination attempt (PID=${record.pid})`);
|
|
298
|
+
}
|
|
299
|
+
clearPidRecord(this.layout.pidFile);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { errorMessage } from "../error-utils.js";
|
|
2
|
+
import { isPlainObject } from "../json-shape.js";
|
|
3
|
+
import { findExecutableOnPath, runSanitizedCommandAsync, windowsSystemExecutable, } from "../process.js";
|
|
4
|
+
import { DEFAULT_BYPASS_LIST } from "./types.js";
|
|
5
|
+
const MAX_PROXY_STRING_LENGTH = 4096;
|
|
6
|
+
const MAX_SERVICE_NAME_LENGTH = 512;
|
|
7
|
+
export { errorMessage, isPlainObject };
|
|
8
|
+
export function parseProxyString(value, label, allowEmpty = true) {
|
|
9
|
+
if (typeof value !== "string") {
|
|
10
|
+
throw new Error(`Invalid system proxy snapshot: ${label} must be a string`);
|
|
11
|
+
}
|
|
12
|
+
if ((!allowEmpty && value.length === 0) || value.length > MAX_PROXY_STRING_LENGTH) {
|
|
13
|
+
throw new Error(`Invalid system proxy snapshot: ${label} has an invalid length`);
|
|
14
|
+
}
|
|
15
|
+
for (const character of value) {
|
|
16
|
+
const code = character.charCodeAt(0);
|
|
17
|
+
if (code < 0x20 || code === 0x7f) {
|
|
18
|
+
throw new Error(`Invalid system proxy snapshot: ${label} contains control characters`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
export function parseServiceName(value, label) {
|
|
24
|
+
const service = parseProxyString(value, label, false);
|
|
25
|
+
if (service.length > MAX_SERVICE_NAME_LENGTH) {
|
|
26
|
+
throw new Error(`Invalid system proxy snapshot: ${label} has an invalid length`);
|
|
27
|
+
}
|
|
28
|
+
return service;
|
|
29
|
+
}
|
|
30
|
+
export function parseSnapshotPort(value, label) {
|
|
31
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > 65_535) {
|
|
32
|
+
throw new Error(`Invalid system proxy snapshot: ${label} must be an integer from 0 to 65535`);
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
export function parseProxyEnable(value) {
|
|
37
|
+
if (value === null)
|
|
38
|
+
return null;
|
|
39
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > 0xffff_ffff) {
|
|
40
|
+
throw new Error("Invalid system proxy snapshot: proxyEnable must be null or an unsigned 32-bit integer");
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
export function compareStrings(a, b) {
|
|
45
|
+
if (a < b)
|
|
46
|
+
return -1;
|
|
47
|
+
if (a > b)
|
|
48
|
+
return 1;
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
export function isSystemProxySupported(platform = process.platform) {
|
|
52
|
+
if (platform === "win32" || platform === "darwin")
|
|
53
|
+
return true;
|
|
54
|
+
if (platform !== "linux")
|
|
55
|
+
return false;
|
|
56
|
+
return findExecutableOnPath("gsettings") !== undefined;
|
|
57
|
+
}
|
|
58
|
+
function resolveSystemProxyCommand(command) {
|
|
59
|
+
if (process.platform === "win32" && command.toLowerCase() === "reg.exe") {
|
|
60
|
+
return windowsSystemExecutable("reg.exe");
|
|
61
|
+
}
|
|
62
|
+
if (process.platform === "darwin" && command === "networksetup") {
|
|
63
|
+
return "/usr/sbin/networksetup";
|
|
64
|
+
}
|
|
65
|
+
if (process.platform === "linux" && command === "gsettings") {
|
|
66
|
+
const resolved = findExecutableOnPath(command);
|
|
67
|
+
if (!resolved)
|
|
68
|
+
throw new Error("gsettings is not available on this system");
|
|
69
|
+
return resolved;
|
|
70
|
+
}
|
|
71
|
+
return command;
|
|
72
|
+
}
|
|
73
|
+
export function runCmd(cmd, args, timeoutMs = 5000) {
|
|
74
|
+
return runSanitizedCommandAsync(resolveSystemProxyCommand(cmd), args, { timeoutMs });
|
|
75
|
+
}
|
|
76
|
+
export function normalizeEnableOptions(opts) {
|
|
77
|
+
if (!isPlainObject(opts)) {
|
|
78
|
+
throw new Error("System proxy options must be a plain object");
|
|
79
|
+
}
|
|
80
|
+
if (typeof opts.port !== "number" ||
|
|
81
|
+
!Number.isInteger(opts.port) ||
|
|
82
|
+
opts.port < 1 ||
|
|
83
|
+
opts.port > 65_535) {
|
|
84
|
+
throw new Error("System proxy port must be an integer from 1 to 65535");
|
|
85
|
+
}
|
|
86
|
+
const host = opts.host === undefined ? "127.0.0.1" : parseProxyString(opts.host, "host", false);
|
|
87
|
+
const rawBypass = opts.bypass ?? DEFAULT_BYPASS_LIST;
|
|
88
|
+
if (!Array.isArray(rawBypass)) {
|
|
89
|
+
throw new Error("System proxy bypass list must be an array of strings");
|
|
90
|
+
}
|
|
91
|
+
const bypass = rawBypass.map((entry, index) => parseProxyString(entry, `bypass[${index}]`, false));
|
|
92
|
+
if (bypass.join(";").length > MAX_PROXY_STRING_LENGTH) {
|
|
93
|
+
throw new Error("System proxy bypass list is too long");
|
|
94
|
+
}
|
|
95
|
+
return { host, port: opts.port, bypass };
|
|
96
|
+
}
|
|
97
|
+
export function formatHostPort(host, port) {
|
|
98
|
+
return host.includes(":") && !host.startsWith("[") ? `[${host}]:${port}` : `${host}:${port}`;
|
|
99
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { compareStrings, errorMessage, formatHostPort, normalizeEnableOptions, parseProxyString, parseServiceName, runCmd, } from "./common.js";
|
|
2
|
+
import { darwinSnapshot } from "./snapshot.js";
|
|
3
|
+
import { SYSTEM_PROXY_SNAPSHOT_VERSION } from "./types.js";
|
|
4
|
+
export function parseDarwinServices(output) {
|
|
5
|
+
return output
|
|
6
|
+
.split(/\r?\n/)
|
|
7
|
+
.map((line) => line.trim())
|
|
8
|
+
.filter((line) => line.length > 0 && !line.startsWith("*") && !line.startsWith("An asterisk"));
|
|
9
|
+
}
|
|
10
|
+
function getDarwinOutputValue(output, label) {
|
|
11
|
+
const prefix = `${label}:`;
|
|
12
|
+
const lowerCasePrefix = prefix.toLowerCase();
|
|
13
|
+
for (const line of output.split(/\r\n?|\n/)) {
|
|
14
|
+
if (line.slice(0, prefix.length).toLowerCase() === lowerCasePrefix) {
|
|
15
|
+
return line.slice(prefix.length).replace(/^[^\S\r\n]+|[^\S\r\n]+$/g, "");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
function parseDarwinProxyOutput(output) {
|
|
21
|
+
const enabledText = getDarwinOutputValue(output, "Enabled")?.toLowerCase();
|
|
22
|
+
const server = getDarwinOutputValue(output, "Server");
|
|
23
|
+
const portText = getDarwinOutputValue(output, "Port");
|
|
24
|
+
const authenticatedText = getDarwinOutputValue(output, "Authenticated Proxy Enabled")?.toLowerCase();
|
|
25
|
+
let enabled;
|
|
26
|
+
if (enabledText === "yes")
|
|
27
|
+
enabled = true;
|
|
28
|
+
if (enabledText === "no")
|
|
29
|
+
enabled = false;
|
|
30
|
+
let port;
|
|
31
|
+
if (portText !== undefined && /^\d+$/.test(portText)) {
|
|
32
|
+
const parsed = Number.parseInt(portText, 10);
|
|
33
|
+
if (parsed >= 0 && parsed <= 65_535)
|
|
34
|
+
port = parsed;
|
|
35
|
+
}
|
|
36
|
+
let authenticated;
|
|
37
|
+
if (authenticatedText === "yes" || authenticatedText === "on" || authenticatedText === "1") {
|
|
38
|
+
authenticated = true;
|
|
39
|
+
}
|
|
40
|
+
if (authenticatedText === "no" || authenticatedText === "off" || authenticatedText === "0") {
|
|
41
|
+
authenticated = false;
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
...(enabled === undefined ? {} : { enabled }),
|
|
45
|
+
...(server === undefined ? {} : { server }),
|
|
46
|
+
...(port === undefined ? {} : { port }),
|
|
47
|
+
...(authenticated === undefined ? {} : { authenticated }),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function parseDarwinProxySetting(output) {
|
|
51
|
+
const parsed = parseDarwinProxyOutput(output);
|
|
52
|
+
if (parsed.enabled === undefined) {
|
|
53
|
+
throw new Error("Invalid macOS networksetup output: missing Enabled state");
|
|
54
|
+
}
|
|
55
|
+
if (parsed.server === undefined) {
|
|
56
|
+
throw new Error("Invalid macOS networksetup output: missing Server value");
|
|
57
|
+
}
|
|
58
|
+
if (parsed.port === undefined) {
|
|
59
|
+
throw new Error("Invalid macOS networksetup output: missing or invalid Port value");
|
|
60
|
+
}
|
|
61
|
+
if (parsed.authenticated === undefined) {
|
|
62
|
+
throw new Error("Invalid macOS networksetup output: missing authenticated proxy state");
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
enabled: parsed.enabled,
|
|
66
|
+
server: parseProxyString(parsed.server, "macOS proxy server"),
|
|
67
|
+
port: parsed.port,
|
|
68
|
+
authenticated: parsed.authenticated,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export function parseDarwinAutoProxySetting(output) {
|
|
72
|
+
const enabledText = getDarwinOutputValue(output, "Enabled")?.toLowerCase();
|
|
73
|
+
const urlText = getDarwinOutputValue(output, "URL");
|
|
74
|
+
if (enabledText !== "yes" && enabledText !== "no") {
|
|
75
|
+
throw new Error("Invalid macOS networksetup output: missing automatic proxy state");
|
|
76
|
+
}
|
|
77
|
+
if (urlText === undefined) {
|
|
78
|
+
throw new Error("Invalid macOS networksetup output: missing automatic proxy URL");
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
enabled: enabledText === "yes",
|
|
82
|
+
url: parseProxyString(urlText === "(null)" ? "" : urlText, "macOS automatic proxy URL"),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async function listDarwinServices(run) {
|
|
86
|
+
const services = parseDarwinServices(await run("networksetup", ["-listallnetworkservices"]));
|
|
87
|
+
const canonical = services.map((service, index) => parseServiceName(service, `network service ${index}`));
|
|
88
|
+
canonical.sort(compareStrings);
|
|
89
|
+
for (let index = 1; index < canonical.length; index++) {
|
|
90
|
+
if (canonical[index - 1] === canonical[index]) {
|
|
91
|
+
throw new Error("Invalid macOS networksetup output: duplicate network service name");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return canonical;
|
|
95
|
+
}
|
|
96
|
+
const DARWIN_PROXY_COMMANDS = [
|
|
97
|
+
{
|
|
98
|
+
kind: "web",
|
|
99
|
+
set: "-setwebproxy",
|
|
100
|
+
setState: "-setwebproxystate",
|
|
101
|
+
get: "-getwebproxy",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
kind: "secureWeb",
|
|
105
|
+
set: "-setsecurewebproxy",
|
|
106
|
+
setState: "-setsecurewebproxystate",
|
|
107
|
+
get: "-getsecurewebproxy",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
kind: "socks",
|
|
111
|
+
set: "-setsocksfirewallproxy",
|
|
112
|
+
setState: "-setsocksfirewallproxystate",
|
|
113
|
+
get: "-getsocksfirewallproxy",
|
|
114
|
+
},
|
|
115
|
+
];
|
|
116
|
+
export async function captureDarwinSnapshot(run = runCmd) {
|
|
117
|
+
const services = [];
|
|
118
|
+
for (const service of await listDarwinServices(run)) {
|
|
119
|
+
const settings = new Map();
|
|
120
|
+
for (const command of DARWIN_PROXY_COMMANDS) {
|
|
121
|
+
settings.set(command.kind, parseDarwinProxySetting(await run("networksetup", [command.get, service])));
|
|
122
|
+
}
|
|
123
|
+
const web = settings.get("web");
|
|
124
|
+
const secureWeb = settings.get("secureWeb");
|
|
125
|
+
const socks = settings.get("socks");
|
|
126
|
+
if (!web || !secureWeb || !socks) {
|
|
127
|
+
throw new Error("Failed to capture all macOS proxy protocols");
|
|
128
|
+
}
|
|
129
|
+
const auto = parseDarwinAutoProxySetting(await run("networksetup", ["-getautoproxyurl", service]));
|
|
130
|
+
services.push({ service, web, secureWeb, socks, auto });
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
version: SYSTEM_PROXY_SNAPSHOT_VERSION,
|
|
134
|
+
platform: "darwin",
|
|
135
|
+
services,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function assertNoAuthenticatedDarwinProxy(snapshot) {
|
|
139
|
+
for (const service of snapshot.services) {
|
|
140
|
+
for (const command of DARWIN_PROXY_COMMANDS) {
|
|
141
|
+
if (service[command.kind].authenticated) {
|
|
142
|
+
throw new Error(`Cannot safely take over macOS proxy for ${JSON.stringify(service.service)}: ${command.kind} uses an authenticated proxy whose credentials cannot be restored`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
export async function applyDarwinSnapshot(value, run = runCmd) {
|
|
148
|
+
const snapshot = darwinSnapshot(value);
|
|
149
|
+
assertNoAuthenticatedDarwinProxy(snapshot);
|
|
150
|
+
const activeServices = await listDarwinServices(run);
|
|
151
|
+
const snapshotServices = snapshot.services.map((service) => service.service);
|
|
152
|
+
if (activeServices.length !== snapshotServices.length ||
|
|
153
|
+
activeServices.some((service, index) => service !== snapshotServices[index])) {
|
|
154
|
+
throw new Error("macOS network service collection changed; refusing to apply a stale proxy snapshot");
|
|
155
|
+
}
|
|
156
|
+
const failures = [];
|
|
157
|
+
for (const service of snapshot.services) {
|
|
158
|
+
for (const command of DARWIN_PROXY_COMMANDS) {
|
|
159
|
+
const setting = service[command.kind];
|
|
160
|
+
try {
|
|
161
|
+
await run("networksetup", [
|
|
162
|
+
command.set,
|
|
163
|
+
service.service,
|
|
164
|
+
setting.server,
|
|
165
|
+
String(setting.port),
|
|
166
|
+
]);
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
failures.push(`${JSON.stringify(service.service)} ${command.kind} server/port: ${errorMessage(err)}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (service.auto.url) {
|
|
173
|
+
try {
|
|
174
|
+
await run("networksetup", ["-setautoproxyurl", service.service, service.auto.url]);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
failures.push(`${JSON.stringify(service.service)} automatic proxy URL: ${errorMessage(err)}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const stateOperations = [
|
|
181
|
+
...DARWIN_PROXY_COMMANDS.map((command) => ({
|
|
182
|
+
label: command.kind,
|
|
183
|
+
command: command.setState,
|
|
184
|
+
enabled: service[command.kind].enabled,
|
|
185
|
+
})),
|
|
186
|
+
{
|
|
187
|
+
label: "automatic proxy",
|
|
188
|
+
command: "-setautoproxystate",
|
|
189
|
+
enabled: service.auto.enabled,
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
for (const operation of [
|
|
193
|
+
...stateOperations.filter((item) => !item.enabled),
|
|
194
|
+
...stateOperations.filter((item) => item.enabled),
|
|
195
|
+
]) {
|
|
196
|
+
try {
|
|
197
|
+
await run("networksetup", [
|
|
198
|
+
operation.command,
|
|
199
|
+
service.service,
|
|
200
|
+
operation.enabled ? "on" : "off",
|
|
201
|
+
]);
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
failures.push(`${JSON.stringify(service.service)} ${operation.label} state: ${errorMessage(err)}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (failures.length > 0) {
|
|
209
|
+
throw new Error(`Failed to apply macOS system proxy settings: ${failures.join("; ")}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
export function createDarwinTarget(original, opts) {
|
|
213
|
+
const snapshot = darwinSnapshot(original);
|
|
214
|
+
assertNoAuthenticatedDarwinProxy(snapshot);
|
|
215
|
+
if (snapshot.services.length === 0) {
|
|
216
|
+
throw new Error("Cannot enable macOS system proxy: no active network services found");
|
|
217
|
+
}
|
|
218
|
+
const normalized = normalizeEnableOptions(opts);
|
|
219
|
+
const targetSetting = () => ({
|
|
220
|
+
enabled: true,
|
|
221
|
+
server: normalized.host,
|
|
222
|
+
port: normalized.port,
|
|
223
|
+
authenticated: false,
|
|
224
|
+
});
|
|
225
|
+
return {
|
|
226
|
+
version: SYSTEM_PROXY_SNAPSHOT_VERSION,
|
|
227
|
+
platform: "darwin",
|
|
228
|
+
services: snapshot.services.map((service) => ({
|
|
229
|
+
service: service.service,
|
|
230
|
+
web: targetSetting(),
|
|
231
|
+
secureWeb: targetSetting(),
|
|
232
|
+
socks: targetSetting(),
|
|
233
|
+
auto: { ...service.auto, enabled: false },
|
|
234
|
+
})),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
export function darwinState(value) {
|
|
238
|
+
const snapshot = darwinSnapshot(value);
|
|
239
|
+
if (snapshot.services.length === 0) {
|
|
240
|
+
return { supported: true, enabled: false, details: "no active network services found" };
|
|
241
|
+
}
|
|
242
|
+
const enabledSettings = [];
|
|
243
|
+
const enabledAutoUrls = [];
|
|
244
|
+
for (const service of snapshot.services) {
|
|
245
|
+
for (const command of DARWIN_PROXY_COMMANDS) {
|
|
246
|
+
const setting = service[command.kind];
|
|
247
|
+
if (setting.enabled)
|
|
248
|
+
enabledSettings.push(setting);
|
|
249
|
+
}
|
|
250
|
+
if (service.auto.enabled)
|
|
251
|
+
enabledAutoUrls.push(service.auto.url);
|
|
252
|
+
}
|
|
253
|
+
if (enabledSettings.length === 0 && enabledAutoUrls.length === 0) {
|
|
254
|
+
return { supported: true, enabled: false };
|
|
255
|
+
}
|
|
256
|
+
if (enabledSettings.length === 0) {
|
|
257
|
+
const urls = new Set(enabledAutoUrls.filter(Boolean));
|
|
258
|
+
return {
|
|
259
|
+
supported: true,
|
|
260
|
+
enabled: true,
|
|
261
|
+
...(urls.size === 1 ? { server: urls.values().next().value } : {}),
|
|
262
|
+
details: "automatic proxy configuration is enabled",
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const endpoints = new Set();
|
|
266
|
+
let hasIncompleteEndpoint = false;
|
|
267
|
+
for (const setting of enabledSettings) {
|
|
268
|
+
if (!setting.server || setting.port === 0) {
|
|
269
|
+
hasIncompleteEndpoint = true;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
endpoints.add(formatHostPort(setting.server, setting.port));
|
|
273
|
+
}
|
|
274
|
+
if (endpoints.size === 1 && !hasIncompleteEndpoint && enabledAutoUrls.length === 0) {
|
|
275
|
+
const server = endpoints.values().next().value;
|
|
276
|
+
return typeof server === "string"
|
|
277
|
+
? { supported: true, enabled: true, server }
|
|
278
|
+
: { supported: true, enabled: true };
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
supported: true,
|
|
282
|
+
enabled: true,
|
|
283
|
+
details: enabledAutoUrls.length > 0
|
|
284
|
+
? "manual and automatic proxy settings are both active"
|
|
285
|
+
: "multiple or incomplete active proxy settings",
|
|
286
|
+
};
|
|
287
|
+
}
|