termdock 1.4.174 → 1.4.176
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,223 @@
|
|
|
1
|
+
/** End-to-end byte transport. Cryptographic handshake and records are owned by noise-libp2p. */
|
|
2
|
+
import { noise, pureJsCrypto } from '@chainsafe/libp2p-noise';
|
|
3
|
+
import { generateKeyPair, privateKeyFromProtobuf, privateKeyToProtobuf } from '@libp2p/crypto/keys';
|
|
4
|
+
import { peerIdFromPublicKey, peerIdFromString } from '@libp2p/peer-id';
|
|
5
|
+
import { AbstractMessageStream } from '@libp2p/utils';
|
|
6
|
+
export const SECURE_PROTOCOL = 'termdock-e2ee-v1';
|
|
7
|
+
export const MAX_SECURE_CONNECTION_AGE_MS = 60 * 60 * 1000;
|
|
8
|
+
export const MAX_SECURE_CONNECTION_BYTES_PER_DIRECTION = 512 * 1024 * 1024;
|
|
9
|
+
const MAX_BUFFER_BYTES = 4 * 1024 * 1024;
|
|
10
|
+
const noop = () => { };
|
|
11
|
+
const silentLogger = Object.assign(noop, {
|
|
12
|
+
enabled: false, error: noop, trace: noop, newScope: () => silentLogger,
|
|
13
|
+
});
|
|
14
|
+
Object.freeze(silentLogger);
|
|
15
|
+
export async function createIdentity() {
|
|
16
|
+
const privateKey = await generateKeyPair('Ed25519');
|
|
17
|
+
return { privateKey, peerId: peerIdFromPublicKey(privateKey.publicKey).toString() };
|
|
18
|
+
}
|
|
19
|
+
/** Contains a private key. Store with device-private permissions, never include in pairing URLs. */
|
|
20
|
+
export function exportIdentity(identity) {
|
|
21
|
+
return btoa(String.fromCharCode(...privateKeyToProtobuf(identity.privateKey)));
|
|
22
|
+
}
|
|
23
|
+
export function importIdentity(serialized) {
|
|
24
|
+
if (serialized.length > 512 || !/^[A-Za-z0-9+/]+={0,2}$/.test(serialized))
|
|
25
|
+
throw new Error('Invalid identity encoding');
|
|
26
|
+
const privateKey = privateKeyFromProtobuf(Uint8Array.from(atob(serialized), c => c.charCodeAt(0)));
|
|
27
|
+
if (privateKey.type !== 'Ed25519')
|
|
28
|
+
throw new Error('Only Ed25519 device identities are supported');
|
|
29
|
+
return { privateKey, peerId: peerIdFromPublicKey(privateKey.publicKey).toString() };
|
|
30
|
+
}
|
|
31
|
+
/** Bounded bridge, not a cryptographic record layer. */
|
|
32
|
+
class ByteQueue {
|
|
33
|
+
consumed;
|
|
34
|
+
chunks = [];
|
|
35
|
+
size = 0;
|
|
36
|
+
ended = false;
|
|
37
|
+
error;
|
|
38
|
+
wake;
|
|
39
|
+
constructor(consumed = noop) {
|
|
40
|
+
this.consumed = consumed;
|
|
41
|
+
}
|
|
42
|
+
push(bytes) {
|
|
43
|
+
if (this.ended)
|
|
44
|
+
throw this.error ?? new Error('Byte stream closed');
|
|
45
|
+
if (this.size + bytes.byteLength > MAX_BUFFER_BYTES)
|
|
46
|
+
throw new Error('Secure stream buffer limit exceeded');
|
|
47
|
+
this.chunks.push(bytes.slice());
|
|
48
|
+
this.size += bytes.byteLength;
|
|
49
|
+
this.wake?.();
|
|
50
|
+
}
|
|
51
|
+
end(error) { this.ended = true; this.error ??= error; this.wake?.(); }
|
|
52
|
+
async *[Symbol.asyncIterator]() {
|
|
53
|
+
while (true) {
|
|
54
|
+
if (this.error)
|
|
55
|
+
throw this.error;
|
|
56
|
+
const chunk = this.chunks.shift();
|
|
57
|
+
if (chunk) {
|
|
58
|
+
this.size -= chunk.byteLength;
|
|
59
|
+
yield chunk;
|
|
60
|
+
this.consumed();
|
|
61
|
+
}
|
|
62
|
+
else if (this.ended)
|
|
63
|
+
return;
|
|
64
|
+
else
|
|
65
|
+
await new Promise(resolve => { this.wake = resolve; });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
class TransportStream extends AbstractMessageStream {
|
|
70
|
+
duplex;
|
|
71
|
+
outgoing = new ByteQueue(() => this.onMuxerDrain());
|
|
72
|
+
finished;
|
|
73
|
+
constructor(duplex, initiator) {
|
|
74
|
+
super({ log: silentLogger, direction: initiator ? 'outbound' : 'inbound', maxReadBufferLength: MAX_BUFFER_BYTES, maxWriteBufferLength: MAX_BUFFER_BYTES });
|
|
75
|
+
this.duplex = duplex;
|
|
76
|
+
this.finished = duplex.sink(this.outgoing).catch(error => { this.abort(asError(error)); });
|
|
77
|
+
void this.readTransport();
|
|
78
|
+
}
|
|
79
|
+
async readTransport() {
|
|
80
|
+
try {
|
|
81
|
+
for await (const bytes of this.duplex.source)
|
|
82
|
+
this.onData(bytes.slice());
|
|
83
|
+
this.onTransportClosed();
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
this.onTransportClosed(asError(error));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
sendData(data) {
|
|
90
|
+
this.outgoing.push(data.subarray());
|
|
91
|
+
return { sentBytes: data.byteLength, canSendMore: false };
|
|
92
|
+
}
|
|
93
|
+
sendReset(error) { this.outgoing.end(error); this.duplex.close?.(error); }
|
|
94
|
+
sendPause() { }
|
|
95
|
+
sendResume() { }
|
|
96
|
+
async close() {
|
|
97
|
+
this.outgoing.end();
|
|
98
|
+
this.duplex.close?.();
|
|
99
|
+
this.onTransportClosed();
|
|
100
|
+
await this.finished;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function asError(value) { return value instanceof Error ? value : new Error('Secure transport failed'); }
|
|
104
|
+
export class SecureConnectionExpiredError extends Error {
|
|
105
|
+
code = 'E2EE_REHANDSHAKE_REQUIRED';
|
|
106
|
+
constructor() { super('Encrypted connection lifetime reached; establish a new handshake without replaying pending input'); }
|
|
107
|
+
}
|
|
108
|
+
function connectionLimits(limits) {
|
|
109
|
+
const maxAgeMs = limits?.maxAgeMs ?? MAX_SECURE_CONNECTION_AGE_MS;
|
|
110
|
+
const maxBytesPerDirection = limits?.maxBytesPerDirection ?? MAX_SECURE_CONNECTION_BYTES_PER_DIRECTION;
|
|
111
|
+
if (!Number.isSafeInteger(maxAgeMs) || maxAgeMs <= 0 || maxAgeMs > MAX_SECURE_CONNECTION_AGE_MS
|
|
112
|
+
|| !Number.isSafeInteger(maxBytesPerDirection) || maxBytesPerDirection <= 0 || maxBytesPerDirection > MAX_SECURE_CONNECTION_BYTES_PER_DIRECTION) {
|
|
113
|
+
throw new Error('Encrypted connection limits cannot exceed the production maximum');
|
|
114
|
+
}
|
|
115
|
+
return { maxAgeMs, maxBytesPerDirection };
|
|
116
|
+
}
|
|
117
|
+
export async function secureConnection(options) {
|
|
118
|
+
const { identity, duplex, initiator, targetPinnedPeerId, signal } = options;
|
|
119
|
+
const limits = connectionLimits(options.limits);
|
|
120
|
+
if (typeof Reflect.get(Promise, 'withResolvers') !== 'function' || typeof globalThis.crypto?.getRandomValues !== 'function') {
|
|
121
|
+
throw new Error('End-to-end encryption requires Node.js 22+ or a modern browser with Promise.withResolvers and secure random support');
|
|
122
|
+
}
|
|
123
|
+
if (initiator && !targetPinnedPeerId)
|
|
124
|
+
throw new Error('A pinned target identity is required');
|
|
125
|
+
const remotePeer = targetPinnedPeerId ? peerIdFromString(targetPinnedPeerId) : undefined;
|
|
126
|
+
if (remotePeer && remotePeer.type !== 'Ed25519')
|
|
127
|
+
throw new Error('Only Ed25519 target identities are supported');
|
|
128
|
+
if (identity.peerId !== peerIdFromPublicKey(identity.privateKey.publicKey).toString())
|
|
129
|
+
throw new Error('Local identity mismatch');
|
|
130
|
+
if (signal?.aborted)
|
|
131
|
+
throw signal.reason ?? new Error('Connection aborted');
|
|
132
|
+
const transport = new TransportStream(duplex, initiator);
|
|
133
|
+
const handshake = new AbortController();
|
|
134
|
+
const timeout = setTimeout(() => {
|
|
135
|
+
const error = new Error('Encrypted handshake timed out');
|
|
136
|
+
handshake.abort(error);
|
|
137
|
+
transport.abort(error);
|
|
138
|
+
}, 15_000);
|
|
139
|
+
const abort = () => { handshake.abort(signal?.reason); transport.abort(asError(signal?.reason)); };
|
|
140
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
141
|
+
transport.addEventListener('close', () => signal?.removeEventListener('abort', abort), { once: true });
|
|
142
|
+
const encrypter = noise({ crypto: pureJsCrypto, prologueBytes: new TextEncoder().encode(SECURE_PROTOCOL) })({
|
|
143
|
+
privateKey: identity.privateKey,
|
|
144
|
+
peerId: peerIdFromPublicKey(identity.privateKey.publicKey),
|
|
145
|
+
logger: { forComponent: () => silentLogger },
|
|
146
|
+
// Only getStreamMuxers is needed, and negotiation is explicitly disabled.
|
|
147
|
+
upgrader: { getStreamMuxers: () => new Map() },
|
|
148
|
+
});
|
|
149
|
+
try {
|
|
150
|
+
const secured = await (initiator ? encrypter.secureOutbound(transport, { remotePeer, signal: handshake.signal, skipStreamMuxerNegotiation: true })
|
|
151
|
+
: encrypter.secureInbound(transport, { remotePeer, signal: handshake.signal, skipStreamMuxerNegotiation: true }));
|
|
152
|
+
if (secured.remotePeer.type !== 'Ed25519')
|
|
153
|
+
throw new Error('Unsupported remote identity');
|
|
154
|
+
return { authenticatedPeerId: secured.remotePeer.toString(), duplex: plaintextDuplex(secured.connection, limits) };
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
transport.abort(asError(error));
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
clearTimeout(timeout);
|
|
162
|
+
signal?.removeEventListener('abort', abort);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function plaintextDuplex(stream, limits) {
|
|
166
|
+
const incoming = new ByteQueue();
|
|
167
|
+
const establishedAt = performance.now(), establishedWallTime = Date.now();
|
|
168
|
+
let sentBytes = 0, receivedBytes = 0, expiredError;
|
|
169
|
+
const expire = () => {
|
|
170
|
+
expiredError ??= new SecureConnectionExpiredError();
|
|
171
|
+
incoming.end(expiredError);
|
|
172
|
+
stream.abort(expiredError);
|
|
173
|
+
return expiredError;
|
|
174
|
+
};
|
|
175
|
+
const lifetime = setTimeout(expire, limits.maxAgeMs);
|
|
176
|
+
if (typeof lifetime === 'object')
|
|
177
|
+
lifetime.unref?.();
|
|
178
|
+
const checkLimit = (alreadyTransferred, nextBytes) => {
|
|
179
|
+
if (expiredError)
|
|
180
|
+
throw expiredError;
|
|
181
|
+
// Check on traffic as well as the timer: a suspended PWA must not resume
|
|
182
|
+
// using an expired session key before its delayed timer gets a turn.
|
|
183
|
+
const age = Math.max(performance.now() - establishedAt, Date.now() - establishedWallTime);
|
|
184
|
+
if (age >= limits.maxAgeMs || alreadyTransferred + nextBytes >= limits.maxBytesPerDirection)
|
|
185
|
+
throw expire();
|
|
186
|
+
};
|
|
187
|
+
stream.addEventListener('message', event => {
|
|
188
|
+
try {
|
|
189
|
+
checkLimit(receivedBytes, event.data.byteLength);
|
|
190
|
+
receivedBytes += event.data.byteLength;
|
|
191
|
+
incoming.push(event.data.subarray());
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
stream.abort(asError(error));
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
stream.addEventListener('close', event => { clearTimeout(lifetime); incoming.end(event.error); }, { once: true });
|
|
198
|
+
let writing = false;
|
|
199
|
+
return {
|
|
200
|
+
source: incoming,
|
|
201
|
+
async sink(source) {
|
|
202
|
+
if (writing)
|
|
203
|
+
throw new Error('Secure sink already consumed');
|
|
204
|
+
writing = true;
|
|
205
|
+
try {
|
|
206
|
+
for await (const chunk of source) {
|
|
207
|
+
checkLimit(sentBytes, chunk.byteLength);
|
|
208
|
+
sentBytes += chunk.byteLength;
|
|
209
|
+
if (!stream.send(chunk))
|
|
210
|
+
await stream.onDrain();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
stream.abort(asError(error));
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
close(error) { if (error)
|
|
219
|
+
stream.abort(error);
|
|
220
|
+
else
|
|
221
|
+
void stream.close(); },
|
|
222
|
+
};
|
|
223
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { AsyncQueue } from './packets.js';
|
|
2
|
+
export function socketDuplex(socket) {
|
|
3
|
+
const input = new AsyncQueue(64);
|
|
4
|
+
socket.binaryType = 'arraybuffer';
|
|
5
|
+
const close = (error) => { input.end(error); socket.close(1000); };
|
|
6
|
+
socket.addEventListener('message', event => {
|
|
7
|
+
try {
|
|
8
|
+
if (!(event.data instanceof ArrayBuffer) || event.data.byteLength > 1024 * 1024)
|
|
9
|
+
throw new Error('Invalid encrypted frame');
|
|
10
|
+
input.push(new Uint8Array(event.data));
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
close(new Error('Encrypted transport rejected frame'));
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
socket.addEventListener('close', () => input.end());
|
|
17
|
+
socket.addEventListener('error', () => input.end(new Error('Encrypted transport disconnected')));
|
|
18
|
+
return {
|
|
19
|
+
source: input, close,
|
|
20
|
+
async sink(source) {
|
|
21
|
+
try {
|
|
22
|
+
for await (const chunk of source) {
|
|
23
|
+
for (let offset = 0; offset < chunk.length; offset += 64 * 1024) {
|
|
24
|
+
const started = Date.now();
|
|
25
|
+
while (socket.bufferedAmount > 256 * 1024) {
|
|
26
|
+
if (socket.readyState !== 1 || Date.now() - started > 15000)
|
|
27
|
+
throw new Error('Encrypted transport congested');
|
|
28
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
29
|
+
}
|
|
30
|
+
if (socket.readyState !== 1)
|
|
31
|
+
throw new Error('Encrypted transport closed');
|
|
32
|
+
socket.send(chunk.subarray(offset, offset + 64 * 1024));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
close(error instanceof Error ? error : new Error('Encrypted transport failed'));
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Long-lived terminal subscriptions have an independent budget from short HTTP
|
|
2
|
+
// operations. Normal browser requests queue below the server's defensive ceiling.
|
|
3
|
+
export const MAX_ACTIVE_HTTP_REQUESTS = 8;
|
|
4
|
+
export const MAX_SERVER_HTTP_REQUESTS = 32;
|
|
5
|
+
export const MAX_OPEN_SECURE_SOCKETS = 128;
|
|
@@ -9,6 +9,7 @@ import busboy from 'busboy';
|
|
|
9
9
|
import { validatePreview } from '../utils/previewValidation.js';
|
|
10
10
|
import { pathValidator } from '../utils/pathValidator.js';
|
|
11
11
|
import { isAuthEnabled, isRequestAuthenticated } from '../utils/authProtection.js';
|
|
12
|
+
import { isEncryptedRequest } from '../federation/requestContext.js';
|
|
12
13
|
import { getImageDimensions, parseImageDimensions } from '../utils/imageDimensions.js';
|
|
13
14
|
import { writeDiffTraceLog, writeErrorLog, writeJsonLog } from '../utils/serverLogger.js';
|
|
14
15
|
import { clearBranchAuditRecords, clearChangeAuditRecords, listBranchAuditRecords, listChangeAuditRecords, buildChangeAuditFingerprint } from '../utils/changeAuditStore.js';
|
|
@@ -3656,7 +3657,7 @@ router.get('/preview/*path', async (req, res) => {
|
|
|
3656
3657
|
// previewed file's directory, so the sandboxed iframe's subresources can
|
|
3657
3658
|
// load without cookies. The token segment in the URL is inherited by every
|
|
3658
3659
|
// relative reference the document makes.
|
|
3659
|
-
if (authEnabled && !maybeToken) {
|
|
3660
|
+
if ((authEnabled || isEncryptedRequest(req)) && !maybeToken) {
|
|
3660
3661
|
const root = stat.isDirectory() ? validatedPath : path.dirname(validatedPath);
|
|
3661
3662
|
const tokenized = tokenizePreviewUrl(previewBaseUrl, mintPreviewToken(root, req.cookies?.[AUTH_COOKIE]));
|
|
3662
3663
|
redirectUrl = stat.isDirectory() && !previewBaseUrl.endsWith('/') ? `${tokenized}/` : tokenized;
|
|
@@ -33,6 +33,8 @@ import { AgentResumeHistoryStore } from '../agent/resumeHistory.js';
|
|
|
33
33
|
import { isTmuxRecoveryCandidate } from '../utils/tmuxRecoveryCandidate.js';
|
|
34
34
|
import { AutomationStore, normalizeAutomationSchedule } from '../agent/automationStore.js';
|
|
35
35
|
import { buildBracketedSubmitBytes, canDeliverPromptToAgent } from '../agent/promptDelivery.js';
|
|
36
|
+
import { collaborationRoutes } from '../agent/collaborationRoutes.js';
|
|
37
|
+
import { COLLAB_LIMITS, extrasFromBody } from '../agent/collaborationProtocol.js';
|
|
36
38
|
import { CollaborationStore } from '../agent/collaborationStore.js';
|
|
37
39
|
import { formatCollaborationDelivery } from '../agent/collaborationPrompt.js';
|
|
38
40
|
import { resolveCollaborationSpawnMode } from '../agent/collaborationSpawn.js';
|
|
@@ -1266,6 +1268,7 @@ function orchestrationSessionSnapshot(record) {
|
|
|
1266
1268
|
const agent = backend?.agent ?? (record.agentResume?.slug ? agentBySlug(record.agentResume.slug) : null);
|
|
1267
1269
|
const latestPrompt = backend?.autoTitlePromptPayloads.at(-1)?.trim() ?? '';
|
|
1268
1270
|
return {
|
|
1271
|
+
...collaborationStore.sessionFacts(record.sessionId, Boolean(backend), backend?.agentSession?.status),
|
|
1269
1272
|
sessionId: record.sessionId,
|
|
1270
1273
|
agentNativeSessionId: backend?.agentSession?.sessionId ?? record.agentResume?.sessionId ?? null,
|
|
1271
1274
|
backendSessionId: record.backendSessionId ?? null,
|
|
@@ -1367,13 +1370,13 @@ function resolveFrontendSessionId(input) {
|
|
|
1367
1370
|
}
|
|
1368
1371
|
function collaborationRemoteSessions() {
|
|
1369
1372
|
return Array.from(new Map(collaborationStore.list().flatMap((group) => group.remoteSessions ?? [])
|
|
1370
|
-
.map((session) => [session.sessionId, { ...session, status: session.serviceConnected !== true || Date.now() - (session.serviceCheckedAt ?? 0) > 15_000 ? 'service-unreachable' : session.status, name: `${session.name} · ${session.serviceLabel}` }])).values());
|
|
1373
|
+
.map((session) => [session.sessionId, { ...session, ...collaborationStore.sessionFacts(session.sessionId, session.serviceConnected === true && Date.now() - (session.serviceCheckedAt ?? 0) <= 15_000, session.status), status: session.serviceConnected !== true || Date.now() - (session.serviceCheckedAt ?? 0) > 15_000 ? 'service-unreachable' : session.status, name: `${session.name} · ${session.serviceLabel}` }])).values());
|
|
1371
1374
|
}
|
|
1372
1375
|
function tryDeliverCollaborationInbox(frontendSessionId) {
|
|
1373
1376
|
const remote = collaborationRemoteSessions().find((session) => session.sessionId === frontendSessionId);
|
|
1374
1377
|
if (remote) {
|
|
1375
1378
|
const unavailable = remote.serviceConnected !== true || Date.now() - (remote.serviceCheckedAt ?? 0) > 15_000;
|
|
1376
|
-
return { delivered: [], pending: collaborationStore.
|
|
1379
|
+
return { delivered: [], pending: collaborationStore.pendingCount(frontendSessionId),
|
|
1377
1380
|
serviceUnavailable: unavailable, reason: unavailable
|
|
1378
1381
|
? `服务 ${remote.serviceLabel} 不可达:消息尚未送达,仅保存在待发送队列;请勿等待对方已收到的回复,服务重连且 Mac 客户端运行后重试投递。`
|
|
1379
1382
|
: '跨服务消息尚未确认送达,正在等待 Mac 客户端转发。' };
|
|
@@ -1400,9 +1403,23 @@ function tryDeliverCollaborationInbox(frontendSessionId) {
|
|
|
1400
1403
|
};
|
|
1401
1404
|
})],
|
|
1402
1405
|
});
|
|
1403
|
-
|
|
1406
|
+
try {
|
|
1407
|
+
session.ptyProcess.write(buildBracketedSubmitBytes(prompt));
|
|
1408
|
+
}
|
|
1409
|
+
catch (error) {
|
|
1410
|
+
for (const message of pending)
|
|
1411
|
+
collaborationStore.recordTransport(message.id, {
|
|
1412
|
+
relay_online: null, peer_reachable: true, attempt_count: (collaborationStore.diagnostic(message.id)?.attempt_count ?? 0) + 1,
|
|
1413
|
+
next_retry_at: Date.now() + 2_000, last_error: `PTY_WRITE_FAILED: ${getErrorMessage(error)}`, checked_at: Date.now(),
|
|
1414
|
+
});
|
|
1415
|
+
setTimeout(() => tryDeliverCollaborationInbox(frontendSessionId), 2_000).unref?.();
|
|
1416
|
+
return { delivered: [], pending: collaborationStore.pendingCount(frontendSessionId), reason: '终端写入失败,消息已保留并等待重试' };
|
|
1417
|
+
}
|
|
1404
1418
|
collaborationStore.markDelivered(pending.map((message) => message.id));
|
|
1405
|
-
|
|
1419
|
+
const remaining = collaborationStore.pendingCount(frontendSessionId);
|
|
1420
|
+
if (remaining)
|
|
1421
|
+
setTimeout(() => tryDeliverCollaborationInbox(frontendSessionId), 300).unref?.();
|
|
1422
|
+
return { delivered: pending.map((message) => message.id), pending: remaining };
|
|
1406
1423
|
}
|
|
1407
1424
|
async function refreshCollaborationAgentIdentity(backendSessionId, session) {
|
|
1408
1425
|
let activeProgram = null;
|
|
@@ -5203,7 +5220,7 @@ router.delete('/operations/automations/:automationId', (req, res) => {
|
|
|
5203
5220
|
// Authenticated desktop connections exchange only collaboration data; no peer credentials
|
|
5204
5221
|
// or arbitrary remote URLs are accepted by the server.
|
|
5205
5222
|
router.get('/operations/collaboration-federation', (_req, res) => {
|
|
5206
|
-
res.json({ ...collaborationStore.federationSnapshot(),
|
|
5223
|
+
res.json({ protocolVersion: 2, limits: COLLAB_LIMITS, ...collaborationStore.federationSnapshot(),
|
|
5207
5224
|
sessions: globalSessionState.sessions.map(orchestrationSessionSnapshot) });
|
|
5208
5225
|
});
|
|
5209
5226
|
router.post('/operations/collaboration-federation', (req, res) => {
|
|
@@ -5218,10 +5235,21 @@ router.post('/operations/collaboration-federation', (req, res) => {
|
|
|
5218
5235
|
}
|
|
5219
5236
|
collaborationStore.mergeFederatedGroup(group);
|
|
5220
5237
|
collaborationStore.mergeFederatedMessages(Array.isArray(req.body.messages) ? req.body.messages : []);
|
|
5238
|
+
const fragments = Array.isArray(req.body.fragments) ? req.body.fragments : [];
|
|
5239
|
+
if (fragments.length > 25 || fragments.some((fragment) => fragment.group_id !== group.id))
|
|
5240
|
+
throw new Error('无效消息分片');
|
|
5241
|
+
const fragmentReceipts = fragments.map((fragment) => collaborationStore.acceptFragment(fragment));
|
|
5242
|
+
for (const entry of Array.isArray(req.body.transport) ? req.body.transport : []) {
|
|
5243
|
+
if (collaborationStore.getMessage(entry.message_id)?.groupId !== group.id || !entry.diagnostic || !Number.isFinite(entry.diagnostic.checked_at) || !Number.isInteger(entry.diagnostic.attempt_count) || entry.diagnostic.attempt_count < 0)
|
|
5244
|
+
continue;
|
|
5245
|
+
collaborationStore.recordTransport(entry.message_id, entry.diagnostic);
|
|
5246
|
+
if (entry.failure_reason)
|
|
5247
|
+
collaborationStore.fail(entry.message_id, entry.failure_reason);
|
|
5248
|
+
}
|
|
5221
5249
|
for (const id of group.sessionIds)
|
|
5222
5250
|
if (localIds.has(id))
|
|
5223
5251
|
tryDeliverCollaborationInbox(id);
|
|
5224
|
-
res.json(collaborationStore.federationSnapshot());
|
|
5252
|
+
res.json({ protocolVersion: 2, ...collaborationStore.federationSnapshot(), fragmentReceipts });
|
|
5225
5253
|
}
|
|
5226
5254
|
catch (error) {
|
|
5227
5255
|
res.status(400).json({ error: getErrorMessage(error) });
|
|
@@ -5283,7 +5311,7 @@ router.post('/operations/collaboration-groups/:groupId/messages', (req, res) =>
|
|
|
5283
5311
|
return res.status(404).json({ error: '协作组不存在' });
|
|
5284
5312
|
const fromSessionId = resolveFrontendSessionId(req.body ?? {});
|
|
5285
5313
|
const kind = typeof req.body?.kind === 'string' ? req.body.kind : 'message';
|
|
5286
|
-
const content = typeof req.body?.content === 'string' ? req.body.content
|
|
5314
|
+
const content = typeof req.body?.content === 'string' ? req.body.content : '';
|
|
5287
5315
|
const requestedTargets = Array.isArray(req.body?.toSessionIds)
|
|
5288
5316
|
? req.body.toSessionIds.filter((id) => typeof id === 'string')
|
|
5289
5317
|
: [];
|
|
@@ -5291,6 +5319,7 @@ router.post('/operations/collaboration-groups/:groupId/messages', (req, res) =>
|
|
|
5291
5319
|
? requestedTargets
|
|
5292
5320
|
: group.sessionIds.filter((id) => id !== fromSessionId);
|
|
5293
5321
|
const messages = collaborationStore.send({
|
|
5322
|
+
...extrasFromBody(req.body),
|
|
5294
5323
|
groupId: group.id,
|
|
5295
5324
|
fromSessionId,
|
|
5296
5325
|
toSessionIds,
|
|
@@ -5303,7 +5332,7 @@ router.post('/operations/collaboration-groups/:groupId/messages', (req, res) =>
|
|
|
5303
5332
|
sessionId,
|
|
5304
5333
|
...tryDeliverCollaborationInbox(sessionId),
|
|
5305
5334
|
}));
|
|
5306
|
-
res.json({ messages, deliveries });
|
|
5335
|
+
res.json({ messages: messages.map((message) => collaborationStore.getMessage(message.id)), deliveries });
|
|
5307
5336
|
}
|
|
5308
5337
|
catch (error) {
|
|
5309
5338
|
res.status(400).json({ error: getErrorMessage(error) });
|
|
@@ -5364,70 +5393,7 @@ router.post('/operations/orchestration/spawn', async (req, res) => {
|
|
|
5364
5393
|
res.status(400).json({ error: getErrorMessage(error) });
|
|
5365
5394
|
}
|
|
5366
5395
|
});
|
|
5367
|
-
router.
|
|
5368
|
-
const sourceSessionId = resolveFrontendSessionId(req.body ?? {});
|
|
5369
|
-
const targetSessionId = typeof req.body?.targetSessionId === 'string' ? req.body.targetSessionId.trim() : '';
|
|
5370
|
-
const content = typeof req.body?.message === 'string' ? req.body.message.trim().slice(0, 20_000) : '';
|
|
5371
|
-
if (!sourceSessionId)
|
|
5372
|
-
return res.status(400).json({ error: '无法识别发送会话;请从 Termdock 会话内运行 td collab' });
|
|
5373
|
-
const group = collaborationStore.groupsForSession(sourceSessionId).find((candidate) => candidate.sessionIds.includes(targetSessionId));
|
|
5374
|
-
if (!group)
|
|
5375
|
-
return res.status(400).json({ error: '发送方和接收方不在同一协作组' });
|
|
5376
|
-
try {
|
|
5377
|
-
const messages = collaborationStore.send({
|
|
5378
|
-
groupId: group.id, fromSessionId: sourceSessionId, toSessionIds: [targetSessionId],
|
|
5379
|
-
kind: typeof req.body?.kind === 'string' ? req.body.kind : 'message',
|
|
5380
|
-
content,
|
|
5381
|
-
});
|
|
5382
|
-
res.json({ ok: true, messages, delivery: tryDeliverCollaborationInbox(targetSessionId) });
|
|
5383
|
-
}
|
|
5384
|
-
catch (error) {
|
|
5385
|
-
res.status(400).json({ error: getErrorMessage(error) });
|
|
5386
|
-
}
|
|
5387
|
-
});
|
|
5388
|
-
router.get('/operations/orchestration/inbox', (req, res) => {
|
|
5389
|
-
const sessionId = resolveFrontendSessionId(req.query);
|
|
5390
|
-
if (!sessionId)
|
|
5391
|
-
return res.status(404).json({ error: '会话不存在' });
|
|
5392
|
-
const messages = collaborationStore.inbox(sessionId, { limit: Number(req.query.limit) || 50 });
|
|
5393
|
-
if (req.query.markRead === 'true')
|
|
5394
|
-
collaborationStore.markRead(messages.map((message) => message.id));
|
|
5395
|
-
const groups = collaborationStore.groupsForSession(sessionId);
|
|
5396
|
-
const peerIds = new Set(groups.flatMap((group) => group.sessionIds).filter((id) => id !== sessionId));
|
|
5397
|
-
res.json({
|
|
5398
|
-
session: orchestrationSessionSnapshot(globalSessionState.sessions.find((record) => record.sessionId === sessionId)),
|
|
5399
|
-
groups,
|
|
5400
|
-
peers: [...globalSessionState.sessions.filter((record) => peerIds.has(record.sessionId)).map(orchestrationSessionSnapshot), ...collaborationRemoteSessions().filter((record) => peerIds.has(record.sessionId))],
|
|
5401
|
-
messages,
|
|
5402
|
-
});
|
|
5403
|
-
});
|
|
5404
|
-
router.post('/operations/orchestration/reply', (req, res) => {
|
|
5405
|
-
const sourceSessionId = resolveFrontendSessionId(req.body ?? {});
|
|
5406
|
-
const messageId = typeof req.body?.messageId === 'string' ? req.body.messageId.trim() : '';
|
|
5407
|
-
const content = typeof req.body?.content === 'string' ? req.body.content.trim().slice(0, 20_000) : '';
|
|
5408
|
-
const original = collaborationStore.getMessage(messageId);
|
|
5409
|
-
if (!sourceSessionId || !original || original.toSessionId !== sourceSessionId) {
|
|
5410
|
-
return res.status(404).json({ error: '协作消息不存在或不属于当前会话' });
|
|
5411
|
-
}
|
|
5412
|
-
if (!original.fromSessionId)
|
|
5413
|
-
return res.status(400).json({ error: '这条用户消息无需回复到其他会话' });
|
|
5414
|
-
try {
|
|
5415
|
-
collaborationStore.markRead([original.id]);
|
|
5416
|
-
const messages = collaborationStore.send({
|
|
5417
|
-
groupId: original.groupId,
|
|
5418
|
-
fromSessionId: sourceSessionId,
|
|
5419
|
-
toSessionIds: [original.fromSessionId],
|
|
5420
|
-
kind: 'reply',
|
|
5421
|
-
content,
|
|
5422
|
-
threadId: original.threadId,
|
|
5423
|
-
replyTo: original.id,
|
|
5424
|
-
});
|
|
5425
|
-
res.json({ ok: true, messages, delivery: tryDeliverCollaborationInbox(original.fromSessionId) });
|
|
5426
|
-
}
|
|
5427
|
-
catch (error) {
|
|
5428
|
-
res.status(400).json({ error: getErrorMessage(error) });
|
|
5429
|
-
}
|
|
5430
|
-
});
|
|
5396
|
+
router.use('/operations/orchestration', collaborationRoutes({ store: collaborationStore, resolveSession: resolveFrontendSessionId, deliver: tryDeliverCollaborationInbox }));
|
|
5431
5397
|
router.get('/operations/session-search', (req, res) => {
|
|
5432
5398
|
const query = typeof req.query.q === 'string' ? req.query.q : '';
|
|
5433
5399
|
const results = sessionSearchStore.search(query, Number(req.query.limit) || 30).map((result) => {
|
|
@@ -7200,6 +7166,8 @@ export function handleTerminalWebSocket(ws, sessionId, clientId, options = {}, i
|
|
|
7200
7166
|
type: 'connected',
|
|
7201
7167
|
streamEpoch: TERMINAL_STREAM_EPOCH,
|
|
7202
7168
|
outputProtocol: options.flowControl ? 2 : undefined,
|
|
7169
|
+
cols: session.cols,
|
|
7170
|
+
rows: session.rows,
|
|
7203
7171
|
runtime,
|
|
7204
7172
|
ptyBackend: session.ptyBackend || 'unknown',
|
|
7205
7173
|
mode: session.mode,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
|
+
import { isEncryptedRequest } from '../federation/requestContext.js';
|
|
2
3
|
import { EventEmitter } from 'events';
|
|
3
4
|
import fs from 'fs';
|
|
4
5
|
import os from 'os';
|
|
@@ -123,6 +124,14 @@ function credentialFingerprint() {
|
|
|
123
124
|
export function isEnvPasswordSet() {
|
|
124
125
|
return getEnvPassword() !== null;
|
|
125
126
|
}
|
|
127
|
+
/** Server-only input for password-authenticated encrypted bootstrap. Never serialize this value. */
|
|
128
|
+
export function getPasswordVerifier() {
|
|
129
|
+
return getEnvPasswordHash() ?? readAuthFile()?.passwordHash ?? null;
|
|
130
|
+
}
|
|
131
|
+
export function getPasswordCredentialFingerprint() {
|
|
132
|
+
const verifier = getPasswordVerifier();
|
|
133
|
+
return verifier ? crypto.createHash('sha256').update(verifier).digest('hex') : null;
|
|
134
|
+
}
|
|
126
135
|
export function isAuthEnabled() {
|
|
127
136
|
if (getEnvPassword())
|
|
128
137
|
return true;
|
|
@@ -317,6 +326,8 @@ export function recordLoginSuccess(ip) {
|
|
|
317
326
|
// ── Express middleware ──
|
|
318
327
|
// Returns true when the incoming request bears a valid session cookie.
|
|
319
328
|
export function isRequestAuthenticated(req) {
|
|
329
|
+
if (isEncryptedRequest(req))
|
|
330
|
+
return true;
|
|
320
331
|
pruneExpiredSessions();
|
|
321
332
|
const token = req.cookies?.[AUTH_COOKIE];
|
|
322
333
|
return isSessionValid(typeof token === 'string' ? token : undefined);
|
|
@@ -338,6 +349,8 @@ export function renewSessionMiddleware(req, res, next) {
|
|
|
338
349
|
// Bypass paths are public (login/status/health/static).
|
|
339
350
|
export function requireAuth(options) {
|
|
340
351
|
return (req, res, next) => {
|
|
352
|
+
if (isEncryptedRequest(req))
|
|
353
|
+
return next();
|
|
341
354
|
if (!isAuthEnabled())
|
|
342
355
|
return next();
|
|
343
356
|
if (options?.bypass?.(req))
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
|
+
import { isEncryptedRequest } from '../federation/requestContext.js';
|
|
2
3
|
import { getCookieSecurityOptions } from './cookieSecurity.js';
|
|
3
4
|
/**
|
|
4
5
|
* CSRF令牌管理器
|
|
@@ -65,6 +66,8 @@ export class CsrfProtection {
|
|
|
65
66
|
*/
|
|
66
67
|
verifyMiddleware(options) {
|
|
67
68
|
return (req, res, next) => {
|
|
69
|
+
if (isEncryptedRequest(req))
|
|
70
|
+
return next();
|
|
68
71
|
if (options?.bypass?.(req)) {
|
|
69
72
|
return next();
|
|
70
73
|
}
|
|
@@ -30,9 +30,14 @@ export const securityHeaders = (req, res, next) => {
|
|
|
30
30
|
// Enforce sandbox even for top-level navigation to user-controlled HTML/SVG.
|
|
31
31
|
// allow-same-origin must NEVER be added: that would expose the terminal API.
|
|
32
32
|
const preview = /^\/api\/terminal\/fs(?:\/|$)/i.test(req.path);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
const host = req.get('host') ?? '';
|
|
34
|
+
const shellSource = /^[a-z0-9.\-:\[\]]+$/i.test(host) ? `http://${host}/preview-shell.html https://${host}/preview-shell.html` : "'none'";
|
|
35
|
+
const shell = req.path === '/preview-shell.html';
|
|
36
|
+
res.setHeader('Content-Security-Policy', shell
|
|
37
|
+
? "sandbox allow-scripts; default-src 'none'; script-src 'unsafe-inline' blob: https: http:; style-src 'unsafe-inline' blob: https:; img-src blob: data: https:; font-src blob: data: https:; media-src blob: data: https:; connect-src https:; frame-src 'none'; frame-ancestors 'self'; object-src 'none'; base-uri https://termdock-preview.invalid; form-action 'none'"
|
|
38
|
+
: preview
|
|
39
|
+
? "sandbox allow-scripts; frame-ancestors 'self'; object-src 'none'; base-uri 'self'"
|
|
40
|
+
: `script-src 'self' 'wasm-unsafe-eval'; script-src-attr 'none'; worker-src 'self' blob:; frame-src ${shellSource}; frame-ancestors 'self'; object-src 'none'; base-uri 'self'`);
|
|
36
41
|
if (getPublicOrigin()) {
|
|
37
42
|
res.setHeader('Strict-Transport-Security', 'max-age=31536000');
|
|
38
43
|
if (!isAuthEnabled()) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "termdock",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.176",
|
|
4
4
|
"description": "A complete web-based terminal application with modern UI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": ".desktop-dist/main.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"dev:client": "vite",
|
|
25
25
|
"dev:server": "cross-env PORT=9835 tsx watch src/server/entry.ts",
|
|
26
26
|
"build": "npm run clean && npm run build:client && npm run build:server && npm run build:runtime-manifest",
|
|
27
|
-
"build:desktop": "node scripts/prepare-desktop-tokens.mjs && tsc -p tsconfig.desktop.json && esbuild desktop/preload.cts --bundle --platform=node --format=cjs --external:electron --outfile=.desktop-dist/preload.cjs",
|
|
27
|
+
"build:desktop": "node scripts/prepare-desktop-tokens.mjs && tsc -p tsconfig.desktop.json && esbuild desktop/preload.cts --bundle --platform=node --format=cjs --external:electron --outfile=.desktop-dist/preload.cjs && esbuild desktop/renderer/services.tsx --bundle --platform=browser --format=iife --minify --outfile=desktop/renderer/services.js",
|
|
28
28
|
"desktop:dev": "npm run build && npm run build:desktop && electron-forge start",
|
|
29
29
|
"desktop:prepare": "npm run build && npm run build:desktop && node scripts/prepare-desktop-runtime.mjs",
|
|
30
30
|
"desktop:make": "node scripts/ensure-macos-appdmg.mjs && npm run desktop:prepare && electron-forge make",
|
|
@@ -44,10 +44,17 @@
|
|
|
44
44
|
"postinstall": "node scripts/postinstall.mjs"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
+
"@chainsafe/libp2p-noise": "17.0.0",
|
|
47
48
|
"@dnd-kit/core": "^6.3.1",
|
|
48
49
|
"@dnd-kit/sortable": "^10.0.0",
|
|
49
50
|
"@dnd-kit/utilities": "^3.2.2",
|
|
50
51
|
"@hello-pangea/dnd": "^18.0.1",
|
|
52
|
+
"@libp2p/crypto": "5.1.9",
|
|
53
|
+
"@libp2p/interface": "3.1.0",
|
|
54
|
+
"@libp2p/peer-id": "6.0.0",
|
|
55
|
+
"@libp2p/utils": "7.0.7",
|
|
56
|
+
"@noble/hashes": "2.4.0",
|
|
57
|
+
"@serenity-kit/opaque": "1.1.0",
|
|
51
58
|
"@types/cookie-parser": "^1.4.10",
|
|
52
59
|
"@types/csurf": "^1.11.5",
|
|
53
60
|
"@types/refractor": "^2.8.0",
|
|
@@ -125,7 +132,7 @@
|
|
|
125
132
|
"vitest": "^4.0.17"
|
|
126
133
|
},
|
|
127
134
|
"engines": {
|
|
128
|
-
"node": ">=
|
|
135
|
+
"node": ">=22.0.0"
|
|
129
136
|
},
|
|
130
137
|
"keywords": [
|
|
131
138
|
"terminal",
|
package/runtime-manifest.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"packageName": "termdock",
|
|
4
|
-
"version": "1.4.
|
|
4
|
+
"version": "1.4.176",
|
|
5
5
|
"runtimeProtocolVersion": 1,
|
|
6
6
|
"minimumDesktopVersion": "1.4.46",
|
|
7
7
|
"nodeMajor": 22,
|
|
8
|
-
"dependencyHash": "sha256
|
|
9
|
-
"serverBundleHash": "sha256-
|
|
10
|
-
"clientBundleHash": "sha256-
|
|
8
|
+
"dependencyHash": "sha256-kSujny2SLOFnZ1ffxm1FYtli7g1vEN8d3vj1oQeLLdc=",
|
|
9
|
+
"serverBundleHash": "sha256-fr1FQxYSw6qb2o5QN42fGPJgN9pA3G6ZfQ5PscJ3b6E=",
|
|
10
|
+
"clientBundleHash": "sha256-f9Xk5CnTO9En7oL7HC2IwM4TdFST+XT+XalrxV1Xors=",
|
|
11
11
|
"entrypoint": "dist/server/cli.js",
|
|
12
12
|
"clientEntrypoint": "dist/client/index.html"
|
|
13
13
|
}
|