termdock 1.4.174 → 1.4.175
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/.desktop-dist/main.js +154 -7
- package/dist/client/assets/App-BXYZyI_k.js +150 -0
- package/dist/client/assets/App-BXYZyI_k.js.br +0 -0
- package/dist/client/assets/App-BXYZyI_k.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DeVtCf73.js +2 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DeVtCf73.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DeVtCf73.js.gz +0 -0
- package/dist/client/assets/DagPlayground-ZIqLZoMo.js +1 -0
- package/dist/client/assets/DagPlayground-ZIqLZoMo.js.br +0 -0
- package/dist/client/assets/DagPlayground-ZIqLZoMo.js.gz +0 -0
- package/dist/client/assets/{DiffLab-CdbPsjKS.js → DiffLab-CSMf8So2.js} +2 -2
- package/dist/client/assets/DiffLab-CSMf8So2.js.br +0 -0
- package/dist/client/assets/DiffLab-CSMf8So2.js.gz +0 -0
- package/dist/client/assets/DiffReview-CE5lW1zQ.js +1 -0
- package/dist/client/assets/DiffReview-CE5lW1zQ.js.br +0 -0
- package/dist/client/assets/DiffReview-CE5lW1zQ.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-CpUW75fZ.js +2 -0
- package/dist/client/assets/DiffReviewLab-CpUW75fZ.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-CpUW75fZ.js.gz +0 -0
- package/dist/client/assets/{DiffViewer-hdxcM9yF.js → DiffViewer-BomvZ4kq.js} +5 -5
- package/dist/client/assets/DiffViewer-BomvZ4kq.js.br +0 -0
- package/dist/client/assets/DiffViewer-BomvZ4kq.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-BZ454pgM.js → ModelPreview-BWdZFyhh.js} +3 -3
- package/dist/client/assets/ModelPreview-BWdZFyhh.js.br +0 -0
- package/dist/client/assets/ModelPreview-BWdZFyhh.js.gz +0 -0
- package/dist/client/assets/RightSidebar-CYVwFbOc.js +79 -0
- package/dist/client/assets/RightSidebar-CYVwFbOc.js.br +0 -0
- package/dist/client/assets/RightSidebar-CYVwFbOc.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-C185Omck.js → abnfDiagram-VCTEODGH-BIPsLStE.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-BIPsLStE.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-BIPsLStE.js.gz +0 -0
- package/dist/client/assets/app-D6KzR8B3.js +34 -0
- package/dist/client/assets/app-D6KzR8B3.js.br +0 -0
- package/dist/client/assets/app-D6KzR8B3.js.gz +0 -0
- package/dist/client/assets/{arc-DNt-DTCl.js → arc-DzeT6hBj.js} +1 -1
- package/dist/client/assets/arc-DzeT6hBj.js.br +0 -0
- package/dist/client/assets/arc-DzeT6hBj.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-C5Nwm_Qz.js → architectureDiagram-5GKGNRK7-B1qsxd_5.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-B1qsxd_5.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-B1qsxd_5.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-C5aR_7V9.js → blockDiagram-I7D4REHJ-NLsa29Jp.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-NLsa29Jp.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-NLsa29Jp.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-DjV6ZNwd.js → c4Diagram-7LVT6UL2-DBAUvou3.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-DBAUvou3.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DBAUvou3.js.gz +0 -0
- package/dist/client/assets/channel-BhwBAWhq.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-Ct7nd4cL.js → chunk-2Q5K7J3B-BsVAXGLa.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-CXem4UYP.js → chunk-5VM5RSS4-BKtILsst.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-DyMNEawE.js → chunk-F27PBJKO-Dn9zcQZG.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-Dn9zcQZG.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-Dn9zcQZG.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-BsW7iIrh.js → chunk-IMKFNOWR-MVtVQeTr.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-MVtVQeTr.js.br +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-BsW7iIrh.js.gz → chunk-IMKFNOWR-MVtVQeTr.js.gz} +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-EO8E1q1J.js → chunk-JWPE2WC7-DliIIMe7.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-0One8T9x.js → chunk-POPQ4Y6H-XBSus3IC.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-CDu41dkj.js → chunk-SVP7TREG-wleSQcJR.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-wleSQcJR.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-wleSQcJR.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-BACjgX9v.js → chunk-TICWLB2K-Dq6LdswN.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-Dq6LdswN.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-Dq6LdswN.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-BJWowF6E.js → chunk-XXDRQBXY-xafyKlM-.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-slQB0ns2.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-slQB0ns2.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-WTwHOCdm.js → cose-bilkent-JH36ORCC-BR7Ohnm_.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-BR7Ohnm_.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-BR7Ohnm_.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-D4y8TkaO.js → cynefin-OW5HDTMX-CmPjQgER.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-CmPjQgER.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-CmPjQgER.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-BzN8Ggk_.js → cynefinDiagram-5FMLGOSQ-CFyR9RvM.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CFyR9RvM.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CFyR9RvM.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-CwMYtt5E.js → dagre-GXQ25YYZ-D_RVMRO1.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-D_RVMRO1.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-D_RVMRO1.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-BnTeHiAb.js → diagram-S7CK7UJ4-6fr-ZoPj.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-6fr-ZoPj.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-6fr-ZoPj.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-BNlh251g.js → diagram-UQ7AKVKN-CgwiUtz0.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-CgwiUtz0.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-CgwiUtz0.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-B5FftXkQ.js → diagram-VSXAHHWV-CLxzTB9k.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-CLxzTB9k.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CLxzTB9k.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-C51ZNXR2.js → diagram-VX7I27RA-DjxDAvdZ.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-DjxDAvdZ.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-DjxDAvdZ.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-CXoZH2qf.js → diagram-Z3DM3KII-CDPOjiz4.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-CDPOjiz4.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-CDPOjiz4.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-C7YFLlx4.js → ebnfDiagram-PWID7BFC-BLbwyF1O.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BLbwyF1O.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BLbwyF1O.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-BNQlS7-g.js → erDiagram-RLTQ6QDP-DbjtZnJx.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-DbjtZnJx.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DbjtZnJx.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-DIQXZ06I.js → flowDiagram-HODETNUW-BirJInAN.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-BirJInAN.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-BirJInAN.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-BaJQfOqD.js → ganttDiagram-EL5Y4UJY-d6Q9vSbv.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-d6Q9vSbv.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-d6Q9vSbv.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-C2J0D0kV.css +1 -0
- package/dist/client/assets/gestureArbiter-C2J0D0kV.css.br +0 -0
- package/dist/client/assets/gestureArbiter-C2J0D0kV.css.gz +0 -0
- package/dist/client/assets/gestureArbiter-D1SNS_gK.js +1 -0
- package/dist/client/assets/gestureArbiter-D1SNS_gK.js.br +0 -0
- package/dist/client/assets/gestureArbiter-D1SNS_gK.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-tQ4E6pwN.js → gitGraphDiagram-WWUBYQGX-BWBoIeS6.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BWBoIeS6.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BWBoIeS6.js.gz +0 -0
- package/dist/client/assets/{index-DlxiaPv1.css → index-DUI8KRf6.css} +1 -1
- package/dist/client/assets/index-DUI8KRf6.css.br +0 -0
- package/dist/client/assets/index-DUI8KRf6.css.gz +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-DTOUeQrN.js → infoDiagram-27XIBGKW-BH4AynkM.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-8sBi-gla.js → ishikawaDiagram-5VMMS53U-BQw90AKR.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-8sBi-gla.js.br → ishikawaDiagram-5VMMS53U-BQw90AKR.js.br} +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BQw90AKR.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-ZwikziWh.js → journeyDiagram-3NMN7TZE-DISkQocf.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DISkQocf.js.br +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-ZwikziWh.js.gz → journeyDiagram-3NMN7TZE-DISkQocf.js.gz} +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-wjuw2jQ4.js → kanban-definition-UXKFOSKX-Qg2LyEVy.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-Qg2LyEVy.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-Qg2LyEVy.js.gz +0 -0
- package/dist/client/assets/{layout-nASMIj-E.js → layout-Cd7-4_Vr.js} +1 -1
- package/dist/client/assets/layout-Cd7-4_Vr.js.br +0 -0
- package/dist/client/assets/layout-Cd7-4_Vr.js.gz +0 -0
- package/dist/client/assets/{linear-pGaQeqW5.js → linear-DFTi2fTn.js} +1 -1
- package/dist/client/assets/linear-DFTi2fTn.js.br +0 -0
- package/dist/client/assets/linear-DFTi2fTn.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-CKEBWbQz.js → mermaid.core-2v726lUU.js} +5 -5
- package/dist/client/assets/mermaid.core-2v726lUU.js.br +0 -0
- package/dist/client/assets/mermaid.core-2v726lUU.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-CBjYcnwd.js → mindmap-definition-YA3MSWOX-_FpwteCy.js} +4 -4
- package/dist/client/assets/mindmap-definition-YA3MSWOX-_FpwteCy.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-_FpwteCy.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-D9Ffc7NY.js +2 -0
- package/dist/client/assets/passwordBootstrap-D9Ffc7NY.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-D9Ffc7NY.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-DJqvclIp.js → pegDiagram-XKGWAZYB-DLFf-HmZ.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-DLFf-HmZ.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DLFf-HmZ.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-DLV4WPwc.js → pieDiagram-E7YTZNPT-DqTnfXHG.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-DqTnfXHG.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-DqTnfXHG.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-BN7Zpsb9.js → quadrantDiagram-AXDQQJYC-B_oBH52Q.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-B_oBH52Q.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-B_oBH52Q.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-DfHX6MUm.js → railroadDiagram-O6MQD6OU-HxFQ7Pce.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-HxFQ7Pce.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-HxFQ7Pce.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-B3NGIjvv.js +1 -0
- package/dist/client/assets/referenceLongPress-B3NGIjvv.js.br +0 -0
- package/dist/client/assets/referenceLongPress-B3NGIjvv.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-tQVSFg_k.js → requirementDiagram-BXWQKSXE-NZdfaZFk.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-NZdfaZFk.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-NZdfaZFk.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-C7JW1-Wh.js → sankeyDiagram-P5KCCOFB-BgCctv6X.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-BgCctv6X.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-BgCctv6X.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-BVeDiFDI.js → sequenceDiagram-WJ2MYXX4-Br42Za1R.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Br42Za1R.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Br42Za1R.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-CBRaCpeP.js → sizeCapture-INFHLROL-FT4z4S1b.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-FT4z4S1b.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-FT4z4S1b.js.gz +0 -0
- package/dist/client/assets/sparkles-BaGaevME.js +1 -0
- package/dist/client/assets/{stateDiagram-D77RDMKH-CiLpGRuf.js → stateDiagram-D77RDMKH-DD0t9f84.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-DD0t9f84.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-DD0t9f84.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-BhvVVB56.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-BkzVtkVY.js → swimlanes-42K2YHIH-DcC8pGLx.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-DcC8pGLx.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-DcC8pGLx.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DSC5lz0i.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-DKyfGITP.js → timeline-definition-24CTP7MA-DnwO_Pxd.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-DnwO_Pxd.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-DnwO_Pxd.js.gz +0 -0
- package/dist/client/assets/triangle-alert-BCQO9Ap9.js +1 -0
- package/dist/client/assets/{vennDiagram-4TSXK5OY-CRgkkrgx.js → vennDiagram-4TSXK5OY-IVNSrZDM.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-IVNSrZDM.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-IVNSrZDM.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-Cr7yS4CZ.js → wardleyDiagram-VM6X3IG4-BnGetaQL.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-BnGetaQL.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-BnGetaQL.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-DkPYl6E0.js → xychartDiagram-S5SC5T6Z-C01eBN-W.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-C01eBN-W.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-C01eBN-W.js.gz +0 -0
- package/dist/client/index.html +2 -2
- package/dist/client/index.html.br +0 -0
- package/dist/client/index.html.gz +0 -0
- package/dist/client/preview-shell.html +1 -0
- package/dist/client/preview-shell.js +20 -0
- package/dist/client/preview-shell.js.br +0 -0
- package/dist/client/preview-shell.js.gz +0 -0
- package/dist/client/sw-secure-api.js +132 -0
- package/dist/client/sw-secure-api.js.br +0 -0
- package/dist/client/sw-secure-api.js.gz +0 -0
- package/dist/client/sw.js +1 -1
- package/dist/client/sw.js.br +0 -0
- package/dist/client/sw.js.gz +0 -0
- package/dist/server/agent/collaborationCli.js +298 -0
- package/dist/server/agent/collaborationPrompt.js +10 -2
- package/dist/server/agent/collaborationProtocol.js +50 -0
- package/dist/server/agent/collaborationRoutes.js +86 -0
- package/dist/server/agent/collaborationStore.js +249 -21
- package/dist/server/cli.js +28 -168
- package/dist/server/entry.js +114 -51
- package/dist/server/federation/accessPolicy.js +122 -0
- package/dist/server/federation/authorization.js +226 -0
- package/dist/server/federation/bootstrapCors.js +23 -0
- package/dist/server/federation/cli.js +185 -0
- package/dist/server/federation/deviceNames.js +29 -0
- package/dist/server/federation/directRoutes.js +72 -0
- package/dist/server/federation/invitations.js +123 -0
- package/dist/server/federation/openAccess.js +25 -0
- package/dist/server/federation/originPolicy.js +19 -0
- package/dist/server/federation/packets.js +116 -0
- package/dist/server/federation/passwordBootstrap.js +86 -0
- package/dist/server/federation/passwordLoginRoutes.js +98 -0
- package/dist/server/federation/relay.js +346 -0
- package/dist/server/federation/requestContext.js +4 -0
- package/dist/server/federation/routeAccess.js +124 -0
- package/dist/server/federation/routeInvitations.js +160 -0
- package/dist/server/federation/runtime.js +537 -0
- package/dist/server/federation/secureProtocol.js +223 -0
- package/dist/server/federation/socketDuplex.js +42 -0
- package/dist/server/federation/streamLimits.js +5 -0
- package/dist/server/routes/filesystem.js +2 -1
- package/dist/server/routes/terminal.js +40 -72
- package/dist/server/utils/authProtection.js +13 -0
- package/dist/server/utils/csrfProtection.js +3 -0
- package/dist/server/utils/publicSecurity.js +8 -3
- package/package.json +10 -3
- package/runtime-manifest.json +4 -4
- package/dist/client/assets/ChangeWalkthroughPanel-hj8I8Cwy.js +0 -2
- package/dist/client/assets/ChangeWalkthroughPanel-hj8I8Cwy.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-hj8I8Cwy.js.gz +0 -0
- package/dist/client/assets/DagPlayground-BRoB8JX5.js +0 -1
- package/dist/client/assets/DagPlayground-BRoB8JX5.js.br +0 -0
- package/dist/client/assets/DagPlayground-BRoB8JX5.js.gz +0 -0
- package/dist/client/assets/DiffLab-CdbPsjKS.js.br +0 -0
- package/dist/client/assets/DiffLab-CdbPsjKS.js.gz +0 -0
- package/dist/client/assets/DiffReview-MQXjrRsW.js +0 -1
- package/dist/client/assets/DiffReview-MQXjrRsW.js.br +0 -0
- package/dist/client/assets/DiffReview-MQXjrRsW.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-C47CNvDM.js +0 -2
- package/dist/client/assets/DiffReviewLab-C47CNvDM.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-C47CNvDM.js.gz +0 -0
- package/dist/client/assets/DiffViewer-hdxcM9yF.js.br +0 -0
- package/dist/client/assets/DiffViewer-hdxcM9yF.js.gz +0 -0
- package/dist/client/assets/ModelPreview-BZ454pgM.js.br +0 -0
- package/dist/client/assets/ModelPreview-BZ454pgM.js.gz +0 -0
- package/dist/client/assets/RightSidebar-i0Oq68YI.js +0 -55
- package/dist/client/assets/RightSidebar-i0Oq68YI.js.br +0 -0
- package/dist/client/assets/RightSidebar-i0Oq68YI.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-C185Omck.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-C185Omck.js.gz +0 -0
- package/dist/client/assets/app-DAp8rVMc.js +0 -175
- package/dist/client/assets/app-DAp8rVMc.js.br +0 -0
- package/dist/client/assets/app-DAp8rVMc.js.gz +0 -0
- package/dist/client/assets/arc-DNt-DTCl.js.br +0 -0
- package/dist/client/assets/arc-DNt-DTCl.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-C5Nwm_Qz.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-C5Nwm_Qz.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-C5aR_7V9.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-C5aR_7V9.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DjV6ZNwd.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DjV6ZNwd.js.gz +0 -0
- package/dist/client/assets/channel-uhLrATdy.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-DyMNEawE.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-DyMNEawE.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-BsW7iIrh.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-CDu41dkj.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-CDu41dkj.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-BACjgX9v.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-BACjgX9v.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-CnxFfzm9.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-CnxFfzm9.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-WTwHOCdm.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-WTwHOCdm.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-D4y8TkaO.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-D4y8TkaO.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BzN8Ggk_.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BzN8Ggk_.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CwMYtt5E.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CwMYtt5E.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-BnTeHiAb.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-BnTeHiAb.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BNlh251g.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BNlh251g.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-B5FftXkQ.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-B5FftXkQ.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-C51ZNXR2.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-C51ZNXR2.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-CXoZH2qf.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-CXoZH2qf.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-C7YFLlx4.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-C7YFLlx4.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-BNQlS7-g.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-BNQlS7-g.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-DIQXZ06I.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-DIQXZ06I.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-BaJQfOqD.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-BaJQfOqD.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-tQ4E6pwN.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-tQ4E6pwN.js.gz +0 -0
- package/dist/client/assets/index-DlxiaPv1.css.br +0 -0
- package/dist/client/assets/index-DlxiaPv1.css.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-8sBi-gla.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-ZwikziWh.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-wjuw2jQ4.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-wjuw2jQ4.js.gz +0 -0
- package/dist/client/assets/layout-nASMIj-E.js.br +0 -0
- package/dist/client/assets/layout-nASMIj-E.js.gz +0 -0
- package/dist/client/assets/linear-pGaQeqW5.js.br +0 -0
- package/dist/client/assets/linear-pGaQeqW5.js.gz +0 -0
- package/dist/client/assets/mermaid.core-CKEBWbQz.js.br +0 -0
- package/dist/client/assets/mermaid.core-CKEBWbQz.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-CBjYcnwd.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-CBjYcnwd.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DJqvclIp.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DJqvclIp.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-DLV4WPwc.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-DLV4WPwc.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BN7Zpsb9.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BN7Zpsb9.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DfHX6MUm.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DfHX6MUm.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-tQVSFg_k.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-tQVSFg_k.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-C7JW1-Wh.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-C7JW1-Wh.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BVeDiFDI.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BVeDiFDI.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CBRaCpeP.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CBRaCpeP.js.gz +0 -0
- package/dist/client/assets/sparkles-D0cwIvB0.js +0 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-CiLpGRuf.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-CiLpGRuf.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-BRbS0rv2.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-BkzVtkVY.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-BkzVtkVY.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-CeWFezkt.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-DKyfGITP.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-DKyfGITP.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-CRgkkrgx.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-CRgkkrgx.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-Cr7yS4CZ.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-Cr7yS4CZ.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-DkPYl6E0.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-DkPYl6E0.js.gz +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { PasswordBootstrapServer } from './passwordBootstrap.js';
|
|
3
|
+
import { getLoginBlockMs, getPasswordVerifier, recordLoginFailure, recordLoginSuccess } from '../utils/authProtection.js';
|
|
4
|
+
import { getPublicOrigin } from '../utils/publicSecurity.js';
|
|
5
|
+
import { bootstrapCors } from './bootstrapCors.js';
|
|
6
|
+
export function createPasswordLoginRouter(getRuntime) {
|
|
7
|
+
const router = express.Router();
|
|
8
|
+
let bootstrap;
|
|
9
|
+
let runtime;
|
|
10
|
+
let computing = 0;
|
|
11
|
+
const starts = [];
|
|
12
|
+
const pending = new Map();
|
|
13
|
+
router.use(bootstrapCors);
|
|
14
|
+
router.use((_req, res, next) => {
|
|
15
|
+
const ready = getRuntime();
|
|
16
|
+
if (!ready) {
|
|
17
|
+
res.status(503).json({ error: 'Encrypted login is starting' });
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (ready !== runtime) {
|
|
21
|
+
runtime = ready;
|
|
22
|
+
bootstrap = new PasswordBootstrapServer({ getPasswordHash: getPasswordVerifier, serverIdentity: ready.serviceId });
|
|
23
|
+
pending.clear();
|
|
24
|
+
}
|
|
25
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
26
|
+
next();
|
|
27
|
+
});
|
|
28
|
+
router.get('/parameters', (_req, res) => {
|
|
29
|
+
try {
|
|
30
|
+
res.json(bootstrap.parameters());
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
res.status(503).json({ error: 'Password login unavailable' });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
router.use(express.json({ limit: '8kb' }));
|
|
37
|
+
router.post('/start', async (req, res) => {
|
|
38
|
+
const ip = req.socket.remoteAddress ?? 'unknown';
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
while (starts.length && starts[0] <= now - 60_000)
|
|
41
|
+
starts.shift();
|
|
42
|
+
for (const [id, entry] of pending)
|
|
43
|
+
if (entry.expiresAt <= now)
|
|
44
|
+
pending.delete(id);
|
|
45
|
+
if (getLoginBlockMs(ip) > 0 || starts.length >= 30 || computing >= 2) {
|
|
46
|
+
res.status(429).json({ error: 'Too many login attempts' });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (typeof req.body?.clientIdentity !== 'string' || typeof req.body?.startLoginRequest !== 'string' ||
|
|
50
|
+
Object.keys(req.body).some(key => !['clientIdentity', 'startLoginRequest'].includes(key))) {
|
|
51
|
+
res.status(400).json({ error: 'Invalid login request' });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const origin = req.headers.origin ?? getPublicOrigin() ?? `${req.protocol}://${req.headers.host}`;
|
|
55
|
+
starts.push(now);
|
|
56
|
+
computing++;
|
|
57
|
+
recordLoginFailure(ip);
|
|
58
|
+
try {
|
|
59
|
+
const result = await bootstrap.start({ ...req.body, origin });
|
|
60
|
+
pending.set(result.attemptId, { ip, origin, expiresAt: Date.now() + 60_000 });
|
|
61
|
+
res.json(result);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
res.status(401).json({ error: 'Password authentication failed' });
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
computing--;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
router.post('/finish', async (req, res) => {
|
|
71
|
+
const ip = req.socket.remoteAddress ?? 'unknown';
|
|
72
|
+
const origin = req.headers.origin ?? getPublicOrigin() ?? `${req.protocol}://${req.headers.host}`;
|
|
73
|
+
if (typeof req.body?.attemptId !== 'string' || typeof req.body?.finishLoginRequest !== 'string' ||
|
|
74
|
+
Object.keys(req.body).some(key => !['attemptId', 'finishLoginRequest'].includes(key))) {
|
|
75
|
+
res.status(400).json({ error: 'Invalid login request' });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const entry = pending.get(req.body.attemptId);
|
|
79
|
+
if (!entry || entry.ip !== ip || entry.origin !== origin || entry.expiresAt <= Date.now()) {
|
|
80
|
+
res.status(401).json({ error: 'Password authentication failed' });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
pending.delete(req.body.attemptId);
|
|
84
|
+
try {
|
|
85
|
+
const credential = getPasswordVerifier();
|
|
86
|
+
const result = await bootstrap.finish(req.body);
|
|
87
|
+
if (!credential || credential !== getPasswordVerifier())
|
|
88
|
+
throw new Error('Password changed');
|
|
89
|
+
runtime.store.grantPassword(result.clientIdentity);
|
|
90
|
+
recordLoginSuccess(ip);
|
|
91
|
+
res.json({ ok: true });
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
res.status(401).json({ error: 'Password authentication failed' });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
return router;
|
|
98
|
+
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
const defaults = { maxFrameBytes: 256 * 1024, maxBufferedBytes: 1024 * 1024,
|
|
4
|
+
maxStreams: 128, heartbeatMs: 30_000, maxRouteTtlMs: 90_000, connectTimeoutMs: 10_000 };
|
|
5
|
+
const identifier = (v) => typeof v === 'string' && /^[a-zA-Z0-9_.:-]{1,160}$/.test(v);
|
|
6
|
+
function parse(raw, limits) {
|
|
7
|
+
if (Buffer.byteLength(raw instanceof Array ? Buffer.concat(raw) : raw) > limits.maxFrameBytes)
|
|
8
|
+
throw new Error('Frame exceeds budget');
|
|
9
|
+
const f = JSON.parse(raw.toString());
|
|
10
|
+
if (f.type === 'register') {
|
|
11
|
+
if (!Array.isArray(f.serviceIds) || f.serviceIds.length > 64 || !f.serviceIds.every(identifier)
|
|
12
|
+
|| !Number.isFinite(f.ttlMs) || f.ttlMs <= 0)
|
|
13
|
+
throw new Error('Invalid registration');
|
|
14
|
+
}
|
|
15
|
+
else if (!identifier(f.streamId) || !['open', 'opened', 'data', 'close'].includes(f.type)
|
|
16
|
+
|| (f.type === 'open' && !identifier(f.serviceId))
|
|
17
|
+
|| (f.type === 'data' && (typeof f.payload !== 'string' || (f.encoding !== undefined && f.encoding !== 'base64'))))
|
|
18
|
+
throw new Error('Invalid frame');
|
|
19
|
+
return f;
|
|
20
|
+
}
|
|
21
|
+
function send(ws, frame, limits) {
|
|
22
|
+
const data = JSON.stringify(frame);
|
|
23
|
+
if (ws.readyState !== 1)
|
|
24
|
+
return false;
|
|
25
|
+
if (Buffer.byteLength(data) > limits.maxFrameBytes || ws.bufferedAmount + Buffer.byteLength(data) > limits.maxBufferedBytes) {
|
|
26
|
+
ws.close(1013, 'Relay budget exceeded');
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
ws.send(data);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
function watch(ws, limits, cleanup) {
|
|
33
|
+
let alive = true;
|
|
34
|
+
const timer = setInterval(() => {
|
|
35
|
+
if (!alive || ws.readyState !== 1) {
|
|
36
|
+
ws.terminate();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
alive = false;
|
|
40
|
+
ws.ping();
|
|
41
|
+
}, limits.heartbeatMs);
|
|
42
|
+
timer.unref?.();
|
|
43
|
+
ws.on('pong', () => { alive = true; });
|
|
44
|
+
ws.once('close', () => { clearInterval(timer); cleanup(); });
|
|
45
|
+
ws.on('error', () => { ws.terminate(); });
|
|
46
|
+
}
|
|
47
|
+
/** In-process wire used only for explicitly configured direct routes. */
|
|
48
|
+
class MemoryWire extends EventEmitter {
|
|
49
|
+
readyState = 1;
|
|
50
|
+
bufferedAmount = 0;
|
|
51
|
+
peer;
|
|
52
|
+
send(data) {
|
|
53
|
+
if (this.readyState !== 1 || this.peer.readyState !== 1)
|
|
54
|
+
return;
|
|
55
|
+
const bytes = Buffer.from(data);
|
|
56
|
+
this.bufferedAmount += bytes.length;
|
|
57
|
+
queueMicrotask(() => {
|
|
58
|
+
this.bufferedAmount -= bytes.length;
|
|
59
|
+
if (this.readyState === 1 && this.peer.readyState === 1)
|
|
60
|
+
this.peer.emit('message', bytes, false);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
ping() { queueMicrotask(() => { if (this.readyState === 1 && this.peer.readyState === 1)
|
|
64
|
+
this.emit('pong'); }); }
|
|
65
|
+
close(code = 1000, reason = '') {
|
|
66
|
+
if (this.readyState === 3)
|
|
67
|
+
return;
|
|
68
|
+
this.readyState = 3;
|
|
69
|
+
this.emit('close', code, Buffer.from(reason));
|
|
70
|
+
this.peer.close(code, reason);
|
|
71
|
+
}
|
|
72
|
+
terminate() { this.close(1006); }
|
|
73
|
+
}
|
|
74
|
+
function memoryPair() {
|
|
75
|
+
const left = new MemoryWire();
|
|
76
|
+
const right = new MemoryWire();
|
|
77
|
+
left.peer = right;
|
|
78
|
+
right.peer = left;
|
|
79
|
+
return [left, right];
|
|
80
|
+
}
|
|
81
|
+
export class RelayRouter {
|
|
82
|
+
options;
|
|
83
|
+
limits;
|
|
84
|
+
peers = new Set();
|
|
85
|
+
routes = new Map();
|
|
86
|
+
sweep;
|
|
87
|
+
directPeers = new WeakMap();
|
|
88
|
+
stopped = false;
|
|
89
|
+
constructor(options) {
|
|
90
|
+
this.options = options;
|
|
91
|
+
this.limits = { ...defaults, ...options };
|
|
92
|
+
this.sweep = setInterval(() => {
|
|
93
|
+
for (const [id, route] of this.routes)
|
|
94
|
+
if (route.expires <= Date.now() || !this.canRegister(route.peer, id))
|
|
95
|
+
this.withdraw(id, route.peer);
|
|
96
|
+
for (const peer of this.peers)
|
|
97
|
+
for (const stream of [...peer.streams.values()]) {
|
|
98
|
+
if (stream.owner === peer && !this.canRoute(peer, stream.serviceId))
|
|
99
|
+
this.closeStream(stream, 'Authorization revoked');
|
|
100
|
+
}
|
|
101
|
+
}, Math.min(this.limits.heartbeatMs, 1000));
|
|
102
|
+
this.sweep.unref?.();
|
|
103
|
+
}
|
|
104
|
+
canRegister(peer, serviceId) {
|
|
105
|
+
return this.directPeers.has(peer) ? this.directPeers.get(peer) === serviceId : this.options.allowRegister(peer.principal, serviceId);
|
|
106
|
+
}
|
|
107
|
+
canRoute(peer, serviceId) {
|
|
108
|
+
return !this.directPeers.has(peer) && this.options.allowRoute(peer.principal, serviceId);
|
|
109
|
+
}
|
|
110
|
+
hasRoute(serviceId) {
|
|
111
|
+
const route = this.routes.get(serviceId);
|
|
112
|
+
return !this.stopped && !!route && route.expires > Date.now() && route.peer.ws.readyState === 1 && this.canRegister(route.peer, serviceId);
|
|
113
|
+
}
|
|
114
|
+
/** Trusted local configuration only. Clients never supply a target URL or factory. */
|
|
115
|
+
registerDirect(serviceId, connect) {
|
|
116
|
+
if (this.stopped || !identifier(serviceId) || typeof connect !== 'function' || this.hasRoute(serviceId))
|
|
117
|
+
throw new Error('Invalid or already registered direct target');
|
|
118
|
+
const [routerWire, clientWire] = memoryPair();
|
|
119
|
+
// The private WeakMap marks this peer; its placeholder principal is never
|
|
120
|
+
// passed to externally supplied authentication/authorization callbacks.
|
|
121
|
+
const peer = { ws: routerWire, principal: undefined, streams: new Map() };
|
|
122
|
+
this.directPeers.set(peer, serviceId);
|
|
123
|
+
this.peers.add(peer);
|
|
124
|
+
watch(routerWire, this.limits, () => this.remove(peer));
|
|
125
|
+
routerWire.on('message', raw => {
|
|
126
|
+
try {
|
|
127
|
+
this.receive(peer, parse(raw, this.limits));
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
routerWire.close(1008, 'Invalid direct route');
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
this.receive(peer, { type: 'register', serviceIds: [serviceId], ttlMs: this.limits.maxRouteTtlMs });
|
|
134
|
+
const client = new RelayClient(clientWire, { ...this.limits, targets: new Map([[serviceId, connect]]) });
|
|
135
|
+
return () => { client.close(); this.remove(peer); routerWire.close(); };
|
|
136
|
+
}
|
|
137
|
+
/** Caller awaits attach before completing its application-level ready handshake. */
|
|
138
|
+
async attach(ws, context) {
|
|
139
|
+
// Reject early application frames rather than buffering untrusted traffic while authenticating.
|
|
140
|
+
let early = false;
|
|
141
|
+
const rejectEarly = () => { early = true; ws.close(1008, 'Authentication pending'); };
|
|
142
|
+
ws.on('message', rejectEarly);
|
|
143
|
+
let principal;
|
|
144
|
+
try {
|
|
145
|
+
principal = await this.options.authenticate(context);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
principal = null;
|
|
149
|
+
}
|
|
150
|
+
ws.off('message', rejectEarly);
|
|
151
|
+
if (principal === null || early || ws.readyState !== 1) {
|
|
152
|
+
ws.close(1008, 'Relay authentication required');
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
const peer = { ws, principal, streams: new Map() };
|
|
156
|
+
this.peers.add(peer);
|
|
157
|
+
watch(ws, this.limits, () => this.remove(peer));
|
|
158
|
+
ws.on('message', raw => {
|
|
159
|
+
try {
|
|
160
|
+
this.receive(peer, parse(raw, this.limits));
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
ws.close(1008, 'Invalid relay request');
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
withdraw(serviceId, peer) {
|
|
169
|
+
if (this.routes.get(serviceId)?.peer === peer)
|
|
170
|
+
this.routes.delete(serviceId);
|
|
171
|
+
for (const stream of [...peer.streams.values()])
|
|
172
|
+
if (stream.serviceId === serviceId && stream.provider === peer)
|
|
173
|
+
this.closeStream(stream, 'Route unavailable');
|
|
174
|
+
}
|
|
175
|
+
remove(peer) {
|
|
176
|
+
this.peers.delete(peer);
|
|
177
|
+
for (const [id, route] of this.routes)
|
|
178
|
+
if (route.peer === peer)
|
|
179
|
+
this.withdraw(id, peer);
|
|
180
|
+
for (const stream of [...peer.streams.values()])
|
|
181
|
+
this.closeStream(stream, 'Relay disconnected');
|
|
182
|
+
}
|
|
183
|
+
closeStream(s, reason) {
|
|
184
|
+
s.owner.streams.delete(s.ownerId);
|
|
185
|
+
s.provider.streams.delete(s.providerId);
|
|
186
|
+
send(s.owner.ws, { type: 'close', streamId: s.ownerId, reason }, this.limits);
|
|
187
|
+
send(s.provider.ws, { type: 'close', streamId: s.providerId, reason }, this.limits);
|
|
188
|
+
}
|
|
189
|
+
receive(peer, f) {
|
|
190
|
+
if (f.type === 'register') {
|
|
191
|
+
if (!f.serviceIds.every(id => this.canRegister(peer, id)))
|
|
192
|
+
throw new Error('Registration denied');
|
|
193
|
+
for (const id of f.serviceIds) {
|
|
194
|
+
const old = this.routes.get(id);
|
|
195
|
+
if (old && old.peer !== peer && old.expires > Date.now())
|
|
196
|
+
throw new Error('Route already registered');
|
|
197
|
+
}
|
|
198
|
+
for (const [id, r] of this.routes)
|
|
199
|
+
if (r.peer === peer && !f.serviceIds.includes(id))
|
|
200
|
+
this.withdraw(id, peer);
|
|
201
|
+
for (const id of f.serviceIds)
|
|
202
|
+
this.routes.set(id, { peer, expires: Date.now() + Math.min(f.ttlMs, this.limits.maxRouteTtlMs) });
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (f.type === 'open') {
|
|
206
|
+
const route = this.routes.get(f.serviceId);
|
|
207
|
+
if (peer.streams.has(f.streamId))
|
|
208
|
+
throw new Error('Duplicate stream');
|
|
209
|
+
if (!this.canRoute(peer, f.serviceId) || !route || route.expires <= Date.now() || route.peer === peer) {
|
|
210
|
+
send(peer.ws, { type: 'close', streamId: f.streamId, reason: 'Route unavailable or denied' }, this.limits);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (peer.streams.size >= this.limits.maxStreams || route.peer.streams.size >= this.limits.maxStreams)
|
|
214
|
+
throw new Error('Stream budget exceeded');
|
|
215
|
+
const s = { owner: peer, ownerId: f.streamId, provider: route.peer, providerId: randomUUID(), serviceId: f.serviceId };
|
|
216
|
+
peer.streams.set(s.ownerId, s);
|
|
217
|
+
route.peer.streams.set(s.providerId, s);
|
|
218
|
+
if (!send(route.peer.ws, { ...f, streamId: s.providerId }, this.limits))
|
|
219
|
+
this.closeStream(s, 'Relay disconnected');
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const s = peer.streams.get(f.streamId);
|
|
223
|
+
if (!s)
|
|
224
|
+
return;
|
|
225
|
+
if (!this.canRoute(s.owner, s.serviceId) || !this.canRegister(s.provider, s.serviceId)) {
|
|
226
|
+
this.closeStream(s, 'Authorization revoked');
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (f.type === 'close') {
|
|
230
|
+
this.closeStream(s, 'Channel closed');
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (f.type === 'opened' && peer !== s.provider)
|
|
234
|
+
throw new Error('Invalid channel direction');
|
|
235
|
+
const target = peer === s.owner ? s.provider : s.owner;
|
|
236
|
+
if (!send(target.ws, { ...f, streamId: peer === s.owner ? s.providerId : s.ownerId }, this.limits))
|
|
237
|
+
this.closeStream(s, 'Relay congestion');
|
|
238
|
+
}
|
|
239
|
+
close() { this.stopped = true; clearInterval(this.sweep); for (const p of [...this.peers]) {
|
|
240
|
+
this.remove(p);
|
|
241
|
+
p.ws.close(1001, 'Router stopped');
|
|
242
|
+
} }
|
|
243
|
+
}
|
|
244
|
+
/** Shared Mac/CLI middle hop. Target factories resolve only after their sockets are open. */
|
|
245
|
+
export class RelayClient {
|
|
246
|
+
upstream;
|
|
247
|
+
options;
|
|
248
|
+
limits;
|
|
249
|
+
streams = new Map();
|
|
250
|
+
renewal;
|
|
251
|
+
stopped = false;
|
|
252
|
+
constructor(upstream, options) {
|
|
253
|
+
this.upstream = upstream;
|
|
254
|
+
this.options = options;
|
|
255
|
+
this.limits = { ...defaults, ...options };
|
|
256
|
+
const ttl = Math.min(options.routeTtlMs ?? 60_000, this.limits.maxRouteTtlMs);
|
|
257
|
+
const register = () => send(upstream, { type: 'register', serviceIds: [...options.targets.keys()], ttlMs: ttl }, this.limits);
|
|
258
|
+
register();
|
|
259
|
+
this.renewal = setInterval(register, Math.max(1, Math.floor(ttl / 2)));
|
|
260
|
+
this.renewal.unref?.();
|
|
261
|
+
watch(upstream, this.limits, () => this.close());
|
|
262
|
+
upstream.on('message', raw => { void this.receive(raw).catch(() => upstream.close(1008, 'Invalid relay frame')); });
|
|
263
|
+
}
|
|
264
|
+
drop(id, reason) {
|
|
265
|
+
const target = this.streams.get(id);
|
|
266
|
+
this.streams.delete(id);
|
|
267
|
+
target?.close();
|
|
268
|
+
send(this.upstream, { type: 'close', streamId: id, reason }, this.limits);
|
|
269
|
+
}
|
|
270
|
+
async receive(raw) {
|
|
271
|
+
const f = parse(raw, this.limits);
|
|
272
|
+
if (f.type === 'register')
|
|
273
|
+
throw new Error('Unexpected registration');
|
|
274
|
+
if (f.type === 'open') {
|
|
275
|
+
const connect = this.options.targets.get(f.serviceId);
|
|
276
|
+
if (!connect || this.streams.has(f.streamId) || this.streams.size >= this.limits.maxStreams) {
|
|
277
|
+
this.drop(f.streamId, 'Target unavailable');
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
this.streams.set(f.streamId, null);
|
|
281
|
+
let timedOut = false;
|
|
282
|
+
const timer = setTimeout(() => { timedOut = true; this.drop(f.streamId, 'Target timeout'); }, this.limits.connectTimeoutMs);
|
|
283
|
+
timer.unref?.();
|
|
284
|
+
let target;
|
|
285
|
+
try {
|
|
286
|
+
target = await connect();
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
clearTimeout(timer);
|
|
290
|
+
this.drop(f.streamId, 'Target unavailable');
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
clearTimeout(timer);
|
|
294
|
+
if (timedOut || this.stopped || !this.streams.has(f.streamId)) {
|
|
295
|
+
target.close();
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
this.streams.set(f.streamId, target);
|
|
299
|
+
watch(target, this.limits, () => { if (this.streams.get(f.streamId) === target)
|
|
300
|
+
this.drop(f.streamId, 'Target disconnected'); });
|
|
301
|
+
target.on('message', (data, isBinary) => {
|
|
302
|
+
if (Buffer.byteLength(data instanceof Array ? Buffer.concat(data) : data) > this.limits.maxFrameBytes) {
|
|
303
|
+
this.drop(f.streamId, 'Unsupported target frame');
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const bytes = data instanceof Array ? Buffer.concat(data) : Buffer.from(data);
|
|
307
|
+
const payload = isBinary ? bytes.toString('base64') : bytes.toString();
|
|
308
|
+
if (!send(this.upstream, { type: 'data', streamId: f.streamId, payload, ...(isBinary ? { encoding: 'base64' } : {}) }, this.limits))
|
|
309
|
+
this.drop(f.streamId, 'Relay congestion');
|
|
310
|
+
});
|
|
311
|
+
send(this.upstream, { type: 'opened', streamId: f.streamId }, this.limits);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (f.type === 'close') {
|
|
315
|
+
this.drop(f.streamId, 'Channel closed');
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (f.type !== 'data')
|
|
319
|
+
throw new Error('Unexpected frame');
|
|
320
|
+
const target = this.streams.get(f.streamId);
|
|
321
|
+
if (!target || target.readyState !== 1) {
|
|
322
|
+
this.drop(f.streamId, 'Target not ready');
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (target.bufferedAmount + Buffer.byteLength(f.payload) > this.limits.maxBufferedBytes) {
|
|
326
|
+
this.drop(f.streamId, 'Target congestion');
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (f.encoding === 'base64') {
|
|
330
|
+
if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(f.payload))
|
|
331
|
+
throw new Error('Invalid ciphertext encoding');
|
|
332
|
+
target.send(Buffer.from(f.payload, 'base64'));
|
|
333
|
+
}
|
|
334
|
+
else
|
|
335
|
+
target.send(f.payload);
|
|
336
|
+
}
|
|
337
|
+
close() {
|
|
338
|
+
if (this.stopped)
|
|
339
|
+
return;
|
|
340
|
+
this.stopped = true;
|
|
341
|
+
clearInterval(this.renewal);
|
|
342
|
+
for (const id of [...this.streams.keys()])
|
|
343
|
+
this.drop(id, 'Relay stopped');
|
|
344
|
+
this.upstream.close(1001, 'Relay stopped');
|
|
345
|
+
}
|
|
346
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, renameSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
4
|
+
const validId = (x) => typeof x === 'string' && /^[a-zA-Z0-9_.:-]{1,160}$/.test(x);
|
|
5
|
+
const digest = (s) => createHash('sha256').update(s).digest();
|
|
6
|
+
/** Target directory is re-read on every permission check; invalid config denies all. */
|
|
7
|
+
export class RouteAccess {
|
|
8
|
+
filePath;
|
|
9
|
+
now;
|
|
10
|
+
canRouteSubject;
|
|
11
|
+
tickets = new Map();
|
|
12
|
+
constructor(filePath, now = Date.now, canRouteSubject = () => false) {
|
|
13
|
+
this.filePath = filePath;
|
|
14
|
+
this.now = now;
|
|
15
|
+
this.canRouteSubject = canRouteSubject;
|
|
16
|
+
}
|
|
17
|
+
registry() {
|
|
18
|
+
const empty = { relays: [], directTargets: [] };
|
|
19
|
+
try {
|
|
20
|
+
const data = JSON.parse(readFileSync(this.filePath, 'utf8'));
|
|
21
|
+
if (!data || typeof data !== 'object' || Array.isArray(data) || Object.keys(data).some(k => !['relays', 'directTargets'].includes(k))
|
|
22
|
+
|| (data.relays !== undefined && !Array.isArray(data.relays)) || (data.directTargets !== undefined && !Array.isArray(data.directTargets)))
|
|
23
|
+
return empty;
|
|
24
|
+
const relays = data.relays ?? [], directTargets = data.directTargets ?? [];
|
|
25
|
+
if (relays.length > 64 || directTargets.length > 64)
|
|
26
|
+
return empty;
|
|
27
|
+
const ids = new Set();
|
|
28
|
+
for (const r of relays) {
|
|
29
|
+
if (!r || Object.keys(r).some(k => !['id', 'token', 'targets'].includes(k)) || !validId(r.id) || ids.has(r.id) || typeof r.token !== 'string' || r.token.length < 32 || /[\r\n]/.test(r.token) || !Array.isArray(r.targets) || r.targets.length > 64 || !r.targets.every(validId))
|
|
30
|
+
return empty;
|
|
31
|
+
ids.add(r.id);
|
|
32
|
+
}
|
|
33
|
+
const targetIds = new Set();
|
|
34
|
+
for (const target of directTargets) {
|
|
35
|
+
if (!target || typeof target !== 'object' || Array.isArray(target) || Object.keys(target).some(key => !['serviceId', 'url', 'caPath'].includes(key))
|
|
36
|
+
|| !validId(target.serviceId) || targetIds.has(target.serviceId) || typeof target.url !== 'string' || (target.caPath !== undefined && typeof target.caPath !== 'string'))
|
|
37
|
+
return empty;
|
|
38
|
+
const url = new URL(target.url);
|
|
39
|
+
if (!['https:', 'wss:'].includes(url.protocol) || url.username || url.password || url.search || url.hash || !['/', '/api/federation/secure'].includes(url.pathname))
|
|
40
|
+
return empty;
|
|
41
|
+
targetIds.add(target.serviceId);
|
|
42
|
+
}
|
|
43
|
+
return { relays, directTargets };
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return empty;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
relays() { return this.registry().relays; }
|
|
50
|
+
issueRouteTicket(subjectId, serviceId) {
|
|
51
|
+
if (!validId(subjectId) || !validId(serviceId) || !this.canRouteSubject(subjectId, serviceId) || !this.hasConfiguredTarget(serviceId))
|
|
52
|
+
throw new Error('ROUTE_UNAVAILABLE');
|
|
53
|
+
for (const [key, value] of this.tickets)
|
|
54
|
+
if (value.expiresAt <= this.now())
|
|
55
|
+
this.tickets.delete(key);
|
|
56
|
+
if (this.tickets.size >= 1024)
|
|
57
|
+
throw new Error('ROUTE_TICKET_LIMIT');
|
|
58
|
+
const routeToken = randomBytes(32).toString('base64url');
|
|
59
|
+
const expiresAt = this.now() + 30_000;
|
|
60
|
+
this.tickets.set(digest(routeToken).toString('hex'), { subjectId, serviceId, expiresAt });
|
|
61
|
+
return { routeToken, expiresAt };
|
|
62
|
+
}
|
|
63
|
+
/** Called once before upgrading. A route ticket is consumed even if expired. */
|
|
64
|
+
authenticate(authorization, routeToken) {
|
|
65
|
+
if (authorization && routeToken)
|
|
66
|
+
return null;
|
|
67
|
+
if (authorization) {
|
|
68
|
+
if (!authorization.startsWith('Bearer ') || authorization.length > 4096)
|
|
69
|
+
return null;
|
|
70
|
+
const tokenHash = digest(authorization.slice(7));
|
|
71
|
+
const relay = this.relays().find(r => timingSafeEqual(digest(r.token), tokenHash));
|
|
72
|
+
return relay ? { kind: 'relay', id: relay.id, tokenHash: tokenHash.toString('hex') } : null;
|
|
73
|
+
}
|
|
74
|
+
if (!routeToken || routeToken.length > 512)
|
|
75
|
+
return null;
|
|
76
|
+
const key = digest(routeToken).toString('hex');
|
|
77
|
+
const ticket = this.tickets.get(key);
|
|
78
|
+
this.tickets.delete(key);
|
|
79
|
+
if (!ticket || ticket.expiresAt <= this.now() || !this.canRouteSubject(ticket.subjectId, ticket.serviceId) || !this.hasConfiguredTarget(ticket.serviceId))
|
|
80
|
+
return null;
|
|
81
|
+
return { kind: 'client', subjectId: ticket.subjectId, serviceId: ticket.serviceId };
|
|
82
|
+
}
|
|
83
|
+
allowRegister(principal, serviceId) {
|
|
84
|
+
return principal.kind === 'relay' && this.relays().some(r => r.id === principal.id && digest(r.token).toString('hex') === principal.tokenHash && r.targets.includes(serviceId));
|
|
85
|
+
}
|
|
86
|
+
allowRoute(principal, serviceId) {
|
|
87
|
+
return principal.kind === 'client' && principal.serviceId === serviceId && this.canRouteSubject(principal.subjectId, serviceId) && this.hasConfiguredTarget(serviceId);
|
|
88
|
+
}
|
|
89
|
+
hasConfiguredTarget(serviceId) {
|
|
90
|
+
if (!validId(serviceId))
|
|
91
|
+
return false;
|
|
92
|
+
const registry = this.registry();
|
|
93
|
+
return registry.relays.some(relay => relay.targets.includes(serviceId)) || registry.directTargets.some(target => target.serviceId === serviceId);
|
|
94
|
+
}
|
|
95
|
+
addDirectTarget(target) {
|
|
96
|
+
const url = new URL(target.url);
|
|
97
|
+
if (!validId(target.serviceId) || url.protocol !== 'https:' || url.username || url.password || url.search || url.hash || url.pathname !== '/')
|
|
98
|
+
throw new Error('INVALID_DIRECT_TARGET');
|
|
99
|
+
let data;
|
|
100
|
+
try {
|
|
101
|
+
data = JSON.parse(readFileSync(this.filePath, 'utf8'));
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (error.code !== 'ENOENT')
|
|
105
|
+
throw error;
|
|
106
|
+
data = {};
|
|
107
|
+
}
|
|
108
|
+
const registry = this.registry();
|
|
109
|
+
if (!data || typeof data !== 'object' || Array.isArray(data) || Object.keys(data).some(key => !['relays', 'directTargets'].includes(key))
|
|
110
|
+
|| (data.relays !== undefined && (!Array.isArray(data.relays) || data.relays.length !== registry.relays.length))
|
|
111
|
+
|| (data.directTargets !== undefined && (!Array.isArray(data.directTargets) || data.directTargets.length !== registry.directTargets.length)))
|
|
112
|
+
throw new Error('INVALID_ROUTE_REGISTRY');
|
|
113
|
+
if (this.hasConfiguredTarget(target.serviceId))
|
|
114
|
+
return;
|
|
115
|
+
if (registry.directTargets.length >= 64)
|
|
116
|
+
throw new Error('ROUTE_TARGET_LIMIT');
|
|
117
|
+
data.directTargets = [...registry.directTargets, { serviceId: target.serviceId, url: url.origin }];
|
|
118
|
+
mkdirSync(dirname(this.filePath), { recursive: true, mode: 0o700 });
|
|
119
|
+
const temporary = `${this.filePath}.tmp`;
|
|
120
|
+
writeFileSync(temporary, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
121
|
+
renameSync(temporary, this.filePath);
|
|
122
|
+
}
|
|
123
|
+
configuredDirectTargets() { return this.registry().directTargets; }
|
|
124
|
+
}
|