termdock 1.4.173 → 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 +204 -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-xuS6rtpv.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-BQGXUr8t.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-BfZzS1_d.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-Dn_lmPcC.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-DUybtKGZ.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-CLEmOHBp.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-O_XM5b9_.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-DlUEslcV.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-DCrP6W79.js → chunk-2Q5K7J3B-BsVAXGLa.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-Zm6_pYQv.js → chunk-5VM5RSS4-BKtILsst.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-DkyD-X9z.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-UvWxXAkh.js → chunk-IMKFNOWR-MVtVQeTr.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-MVtVQeTr.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-MVtVQeTr.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-BjeBAFYM.js → chunk-JWPE2WC7-DliIIMe7.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-CnqrQdZC.js → chunk-POPQ4Y6H-XBSus3IC.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-C4a1s0v4.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-hnmjYAl_.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-xR4LOz28.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-CqY84fFp.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-Bd14zuaD.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-DSHcNXDs.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-CIGWQYeP.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-C9Fg7OLN.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-DoRhl7Fd.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-CI83g3iM.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-Dp2kP4PB.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-KtLMCM-V.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-D_zseu1m.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-DaWUl_Kz.js → erDiagram-RLTQ6QDP-DbjtZnJx.js} +1 -1
- package/dist/client/assets/{erDiagram-RLTQ6QDP-DaWUl_Kz.js.br → erDiagram-RLTQ6QDP-DbjtZnJx.js.br} +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DbjtZnJx.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-BMoJ46zJ.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-lV_iVmoW.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-BVCT9Btd.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-CnKSUg74.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-BVhXOx1S.js → infoDiagram-27XIBGKW-BH4AynkM.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-Cyzfuoys.js → ishikawaDiagram-5VMMS53U-BQw90AKR.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BQw90AKR.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BQw90AKR.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-CEIPDhQF.js → journeyDiagram-3NMN7TZE-DISkQocf.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DISkQocf.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DISkQocf.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-C8OulEOF.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-YN93qRzb.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-D9T4dYh7.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-BdCvvCGV.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-CSpES3Jh.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-6f4vAA1Q.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-BC4Jd-I8.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-B0pnPaEN.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-B-oOamZT.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-DBSyxt0k.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-CcohzEZE.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-D7KS6rq_.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-CaRXtn4g.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-Dz836LqY.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-BK4KAY00.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-DWgxTDhO.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-BV1TIfOp.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-GYClC5F0.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-D1oKV5W0.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 +12 -3
- package/dist/server/agent/collaborationProtocol.js +50 -0
- package/dist/server/agent/collaborationRoutes.js +86 -0
- package/dist/server/agent/collaborationStore.js +311 -20
- package/dist/server/cli.js +28 -163
- 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 +89 -79
- 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-DKj7F0ex.js +0 -2
- package/dist/client/assets/ChangeWalkthroughPanel-DKj7F0ex.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DKj7F0ex.js.gz +0 -0
- package/dist/client/assets/DagPlayground-D2Wui4jd.js +0 -1
- package/dist/client/assets/DagPlayground-D2Wui4jd.js.br +0 -0
- package/dist/client/assets/DagPlayground-D2Wui4jd.js.gz +0 -0
- package/dist/client/assets/DiffLab-xuS6rtpv.js.br +0 -0
- package/dist/client/assets/DiffLab-xuS6rtpv.js.gz +0 -0
- package/dist/client/assets/DiffReview-CAwpqM_w.js +0 -1
- package/dist/client/assets/DiffReview-CAwpqM_w.js.br +0 -0
- package/dist/client/assets/DiffReview-CAwpqM_w.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-BM_uGdyI.js +0 -2
- package/dist/client/assets/DiffReviewLab-BM_uGdyI.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-BM_uGdyI.js.gz +0 -0
- package/dist/client/assets/DiffViewer-BQGXUr8t.js.br +0 -0
- package/dist/client/assets/DiffViewer-BQGXUr8t.js.gz +0 -0
- package/dist/client/assets/ModelPreview-BfZzS1_d.js.br +0 -0
- package/dist/client/assets/ModelPreview-BfZzS1_d.js.gz +0 -0
- package/dist/client/assets/RightSidebar-Cdqt3C7V.js +0 -55
- package/dist/client/assets/RightSidebar-Cdqt3C7V.js.br +0 -0
- package/dist/client/assets/RightSidebar-Cdqt3C7V.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-Dn_lmPcC.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-Dn_lmPcC.js.gz +0 -0
- package/dist/client/assets/app-DDCkFsNe.js +0 -175
- package/dist/client/assets/app-DDCkFsNe.js.br +0 -0
- package/dist/client/assets/app-DDCkFsNe.js.gz +0 -0
- package/dist/client/assets/arc-DUybtKGZ.js.br +0 -0
- package/dist/client/assets/arc-DUybtKGZ.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CLEmOHBp.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CLEmOHBp.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-O_XM5b9_.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-O_XM5b9_.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DlUEslcV.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DlUEslcV.js.gz +0 -0
- package/dist/client/assets/channel-Bk1VkYvd.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-DkyD-X9z.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-DkyD-X9z.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-UvWxXAkh.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-UvWxXAkh.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-C4a1s0v4.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-C4a1s0v4.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-hnmjYAl_.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-hnmjYAl_.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-_tUz3Cxt.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-_tUz3Cxt.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-CqY84fFp.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-CqY84fFp.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-Bd14zuaD.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-Bd14zuaD.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DSHcNXDs.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DSHcNXDs.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CIGWQYeP.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CIGWQYeP.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-C9Fg7OLN.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-C9Fg7OLN.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-DoRhl7Fd.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-DoRhl7Fd.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CI83g3iM.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CI83g3iM.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-Dp2kP4PB.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-Dp2kP4PB.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-KtLMCM-V.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-KtLMCM-V.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-D_zseu1m.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-D_zseu1m.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DaWUl_Kz.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-BMoJ46zJ.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-BMoJ46zJ.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-lV_iVmoW.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-lV_iVmoW.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BVCT9Btd.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BVCT9Btd.js.gz +0 -0
- package/dist/client/assets/index-CnKSUg74.css.br +0 -0
- package/dist/client/assets/index-CnKSUg74.css.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-Cyzfuoys.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-Cyzfuoys.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CEIPDhQF.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CEIPDhQF.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-C8OulEOF.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-C8OulEOF.js.gz +0 -0
- package/dist/client/assets/layout-YN93qRzb.js.br +0 -0
- package/dist/client/assets/layout-YN93qRzb.js.gz +0 -0
- package/dist/client/assets/linear-D9T4dYh7.js.br +0 -0
- package/dist/client/assets/linear-D9T4dYh7.js.gz +0 -0
- package/dist/client/assets/mermaid.core-BdCvvCGV.js.br +0 -0
- package/dist/client/assets/mermaid.core-BdCvvCGV.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-CSpES3Jh.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-CSpES3Jh.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-6f4vAA1Q.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-6f4vAA1Q.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-BC4Jd-I8.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-BC4Jd-I8.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-B0pnPaEN.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-B0pnPaEN.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-B-oOamZT.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-B-oOamZT.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DBSyxt0k.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DBSyxt0k.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CcohzEZE.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CcohzEZE.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D7KS6rq_.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D7KS6rq_.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CaRXtn4g.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CaRXtn4g.js.gz +0 -0
- package/dist/client/assets/sparkles-CUdUt4qy.js +0 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-Dz836LqY.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-Dz836LqY.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-5V5Y3wwn.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-BK4KAY00.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-BK4KAY00.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-Sj-7YK2P.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-DWgxTDhO.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-DWgxTDhO.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-BV1TIfOp.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-BV1TIfOp.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-GYClC5F0.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-GYClC5F0.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-D1oKV5W0.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-D1oKV5W0.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,
|
|
@@ -1365,7 +1368,19 @@ function resolveFrontendSessionId(input) {
|
|
|
1365
1368
|
const tmuxSessionName = typeof input.tmuxSessionName === 'string' ? input.tmuxSessionName.trim() : '';
|
|
1366
1369
|
return globalSessionState.sessions.find((record) => record.tmuxSessionName === tmuxSessionName)?.sessionId ?? null;
|
|
1367
1370
|
}
|
|
1371
|
+
function collaborationRemoteSessions() {
|
|
1372
|
+
return Array.from(new Map(collaborationStore.list().flatMap((group) => group.remoteSessions ?? [])
|
|
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());
|
|
1374
|
+
}
|
|
1368
1375
|
function tryDeliverCollaborationInbox(frontendSessionId) {
|
|
1376
|
+
const remote = collaborationRemoteSessions().find((session) => session.sessionId === frontendSessionId);
|
|
1377
|
+
if (remote) {
|
|
1378
|
+
const unavailable = remote.serviceConnected !== true || Date.now() - (remote.serviceCheckedAt ?? 0) > 15_000;
|
|
1379
|
+
return { delivered: [], pending: collaborationStore.pendingCount(frontendSessionId),
|
|
1380
|
+
serviceUnavailable: unavailable, reason: unavailable
|
|
1381
|
+
? `服务 ${remote.serviceLabel} 不可达:消息尚未送达,仅保存在待发送队列;请勿等待对方已收到的回复,服务重连且 Mac 客户端运行后重试投递。`
|
|
1382
|
+
: '跨服务消息尚未确认送达,正在等待 Mac 客户端转发。' };
|
|
1383
|
+
}
|
|
1369
1384
|
const record = globalSessionState.sessions.find((candidate) => candidate.sessionId === frontendSessionId);
|
|
1370
1385
|
const session = record?.backendSessionId ? terminalSessions.get(record.backendSessionId) : null;
|
|
1371
1386
|
const pending = collaborationStore.inbox(frontendSessionId, { pendingOnly: true, limit: 10 });
|
|
@@ -1378,19 +1393,33 @@ function tryDeliverCollaborationInbox(frontendSessionId) {
|
|
|
1378
1393
|
targetSessionId: frontendSessionId,
|
|
1379
1394
|
messages: pending,
|
|
1380
1395
|
groups: collaborationStore.groupsForSession(frontendSessionId),
|
|
1381
|
-
sessions: globalSessionState.sessions.map((candidate) => {
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1396
|
+
sessions: [...collaborationRemoteSessions(), ...globalSessionState.sessions.map((candidate) => {
|
|
1397
|
+
const snapshot = orchestrationSessionSnapshot(candidate);
|
|
1398
|
+
return {
|
|
1399
|
+
sessionId: snapshot.sessionId,
|
|
1400
|
+
agentNativeSessionId: snapshot.agentNativeSessionId,
|
|
1401
|
+
name: snapshot.name,
|
|
1402
|
+
status: snapshot.status,
|
|
1403
|
+
};
|
|
1404
|
+
})],
|
|
1390
1405
|
});
|
|
1391
|
-
|
|
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
|
+
}
|
|
1392
1418
|
collaborationStore.markDelivered(pending.map((message) => message.id));
|
|
1393
|
-
|
|
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 };
|
|
1394
1423
|
}
|
|
1395
1424
|
async function refreshCollaborationAgentIdentity(backendSessionId, session) {
|
|
1396
1425
|
let activeProgram = null;
|
|
@@ -5188,8 +5217,47 @@ router.delete('/operations/automations/:automationId', (req, res) => {
|
|
|
5188
5217
|
return res.status(404).json({ error: '自动任务不存在' });
|
|
5189
5218
|
res.status(204).send();
|
|
5190
5219
|
});
|
|
5220
|
+
// Authenticated desktop connections exchange only collaboration data; no peer credentials
|
|
5221
|
+
// or arbitrary remote URLs are accepted by the server.
|
|
5222
|
+
router.get('/operations/collaboration-federation', (_req, res) => {
|
|
5223
|
+
res.json({ protocolVersion: 2, limits: COLLAB_LIMITS, ...collaborationStore.federationSnapshot(),
|
|
5224
|
+
sessions: globalSessionState.sessions.map(orchestrationSessionSnapshot) });
|
|
5225
|
+
});
|
|
5226
|
+
router.post('/operations/collaboration-federation', (req, res) => {
|
|
5227
|
+
try {
|
|
5228
|
+
const group = req.body?.group;
|
|
5229
|
+
if (!group || !Array.isArray(group.remoteSessions))
|
|
5230
|
+
throw new Error('跨服务工作组无效');
|
|
5231
|
+
const remoteIds = new Set(group.remoteSessions.map((session) => session.sessionId));
|
|
5232
|
+
const localIds = new Set(globalSessionState.sessions.map((session) => session.sessionId));
|
|
5233
|
+
if (!group.deleted && group.sessionIds.some((id) => !localIds.has(id) && !remoteIds.has(id))) {
|
|
5234
|
+
throw new Error('工作组包含不存在的本服务会话,请刷新成员列表');
|
|
5235
|
+
}
|
|
5236
|
+
collaborationStore.mergeFederatedGroup(group);
|
|
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
|
+
}
|
|
5249
|
+
for (const id of group.sessionIds)
|
|
5250
|
+
if (localIds.has(id))
|
|
5251
|
+
tryDeliverCollaborationInbox(id);
|
|
5252
|
+
res.json({ protocolVersion: 2, ...collaborationStore.federationSnapshot(), fragmentReceipts });
|
|
5253
|
+
}
|
|
5254
|
+
catch (error) {
|
|
5255
|
+
res.status(400).json({ error: getErrorMessage(error) });
|
|
5256
|
+
}
|
|
5257
|
+
});
|
|
5191
5258
|
router.get('/operations/collaboration-groups', (_req, res) => {
|
|
5192
5259
|
res.json({
|
|
5260
|
+
federationVersion: 1,
|
|
5193
5261
|
groups: collaborationStore.list(),
|
|
5194
5262
|
sessions: globalSessionState.sessions.map(orchestrationSessionSnapshot),
|
|
5195
5263
|
});
|
|
@@ -5199,7 +5267,9 @@ router.post('/operations/collaboration-groups', (req, res) => {
|
|
|
5199
5267
|
const sessionIds = Array.isArray(req.body?.sessionIds)
|
|
5200
5268
|
? req.body.sessionIds.filter((id) => typeof id === 'string')
|
|
5201
5269
|
: [];
|
|
5202
|
-
const
|
|
5270
|
+
const existingGroup = typeof req.body?.id === 'string' ? collaborationStore.getGroup(req.body.id) : null;
|
|
5271
|
+
const knownIds = new Set([...globalSessionState.sessions.map((session) => session.sessionId),
|
|
5272
|
+
...(existingGroup?.remoteSessions?.map((session) => session.sessionId) ?? [])]);
|
|
5203
5273
|
const normalizedIds = Array.from(new Set(sessionIds.filter((id) => knownIds.has(id))));
|
|
5204
5274
|
if (!name || normalizedIds.length < 2)
|
|
5205
5275
|
return res.status(400).json({ error: '协作组至少需要两个有效会话' });
|
|
@@ -5241,7 +5311,7 @@ router.post('/operations/collaboration-groups/:groupId/messages', (req, res) =>
|
|
|
5241
5311
|
return res.status(404).json({ error: '协作组不存在' });
|
|
5242
5312
|
const fromSessionId = resolveFrontendSessionId(req.body ?? {});
|
|
5243
5313
|
const kind = typeof req.body?.kind === 'string' ? req.body.kind : 'message';
|
|
5244
|
-
const content = typeof req.body?.content === 'string' ? req.body.content
|
|
5314
|
+
const content = typeof req.body?.content === 'string' ? req.body.content : '';
|
|
5245
5315
|
const requestedTargets = Array.isArray(req.body?.toSessionIds)
|
|
5246
5316
|
? req.body.toSessionIds.filter((id) => typeof id === 'string')
|
|
5247
5317
|
: [];
|
|
@@ -5249,6 +5319,7 @@ router.post('/operations/collaboration-groups/:groupId/messages', (req, res) =>
|
|
|
5249
5319
|
? requestedTargets
|
|
5250
5320
|
: group.sessionIds.filter((id) => id !== fromSessionId);
|
|
5251
5321
|
const messages = collaborationStore.send({
|
|
5322
|
+
...extrasFromBody(req.body),
|
|
5252
5323
|
groupId: group.id,
|
|
5253
5324
|
fromSessionId,
|
|
5254
5325
|
toSessionIds,
|
|
@@ -5261,7 +5332,7 @@ router.post('/operations/collaboration-groups/:groupId/messages', (req, res) =>
|
|
|
5261
5332
|
sessionId,
|
|
5262
5333
|
...tryDeliverCollaborationInbox(sessionId),
|
|
5263
5334
|
}));
|
|
5264
|
-
res.json({ messages, deliveries });
|
|
5335
|
+
res.json({ messages: messages.map((message) => collaborationStore.getMessage(message.id)), deliveries });
|
|
5265
5336
|
}
|
|
5266
5337
|
catch (error) {
|
|
5267
5338
|
res.status(400).json({ error: getErrorMessage(error) });
|
|
@@ -5277,7 +5348,7 @@ router.get('/operations/orchestration/peers', async (req, res) => {
|
|
|
5277
5348
|
res.json({
|
|
5278
5349
|
source: orchestrationSessionSnapshot(source),
|
|
5279
5350
|
groups,
|
|
5280
|
-
peers: globalSessionState.sessions.filter((record) => peerIds.has(record.sessionId)).map(orchestrationSessionSnapshot),
|
|
5351
|
+
peers: [...globalSessionState.sessions.filter((record) => peerIds.has(record.sessionId)).map(orchestrationSessionSnapshot), ...collaborationRemoteSessions().filter((record) => peerIds.has(record.sessionId))],
|
|
5281
5352
|
sessions: globalSessionState.sessions.map(orchestrationSessionSnapshot),
|
|
5282
5353
|
agents: await listDetectedAgentLaunchers(),
|
|
5283
5354
|
});
|
|
@@ -5322,70 +5393,7 @@ router.post('/operations/orchestration/spawn', async (req, res) => {
|
|
|
5322
5393
|
res.status(400).json({ error: getErrorMessage(error) });
|
|
5323
5394
|
}
|
|
5324
5395
|
});
|
|
5325
|
-
router.
|
|
5326
|
-
const sourceSessionId = resolveFrontendSessionId(req.body ?? {});
|
|
5327
|
-
const targetSessionId = typeof req.body?.targetSessionId === 'string' ? req.body.targetSessionId.trim() : '';
|
|
5328
|
-
const content = typeof req.body?.message === 'string' ? req.body.message.trim().slice(0, 20_000) : '';
|
|
5329
|
-
if (!sourceSessionId)
|
|
5330
|
-
return res.status(400).json({ error: '无法识别发送会话;请从 Termdock 会话内运行 td collab' });
|
|
5331
|
-
const group = collaborationStore.groupsForSession(sourceSessionId).find((candidate) => candidate.sessionIds.includes(targetSessionId));
|
|
5332
|
-
if (!group)
|
|
5333
|
-
return res.status(400).json({ error: '发送方和接收方不在同一协作组' });
|
|
5334
|
-
try {
|
|
5335
|
-
const messages = collaborationStore.send({
|
|
5336
|
-
groupId: group.id, fromSessionId: sourceSessionId, toSessionIds: [targetSessionId],
|
|
5337
|
-
kind: typeof req.body?.kind === 'string' ? req.body.kind : 'message',
|
|
5338
|
-
content,
|
|
5339
|
-
});
|
|
5340
|
-
res.json({ ok: true, messages, delivery: tryDeliverCollaborationInbox(targetSessionId) });
|
|
5341
|
-
}
|
|
5342
|
-
catch (error) {
|
|
5343
|
-
res.status(400).json({ error: getErrorMessage(error) });
|
|
5344
|
-
}
|
|
5345
|
-
});
|
|
5346
|
-
router.get('/operations/orchestration/inbox', (req, res) => {
|
|
5347
|
-
const sessionId = resolveFrontendSessionId(req.query);
|
|
5348
|
-
if (!sessionId)
|
|
5349
|
-
return res.status(404).json({ error: '会话不存在' });
|
|
5350
|
-
const messages = collaborationStore.inbox(sessionId, { limit: Number(req.query.limit) || 50 });
|
|
5351
|
-
if (req.query.markRead === 'true')
|
|
5352
|
-
collaborationStore.markRead(messages.map((message) => message.id));
|
|
5353
|
-
const groups = collaborationStore.groupsForSession(sessionId);
|
|
5354
|
-
const peerIds = new Set(groups.flatMap((group) => group.sessionIds).filter((id) => id !== sessionId));
|
|
5355
|
-
res.json({
|
|
5356
|
-
session: orchestrationSessionSnapshot(globalSessionState.sessions.find((record) => record.sessionId === sessionId)),
|
|
5357
|
-
groups,
|
|
5358
|
-
peers: globalSessionState.sessions.filter((record) => peerIds.has(record.sessionId)).map(orchestrationSessionSnapshot),
|
|
5359
|
-
messages,
|
|
5360
|
-
});
|
|
5361
|
-
});
|
|
5362
|
-
router.post('/operations/orchestration/reply', (req, res) => {
|
|
5363
|
-
const sourceSessionId = resolveFrontendSessionId(req.body ?? {});
|
|
5364
|
-
const messageId = typeof req.body?.messageId === 'string' ? req.body.messageId.trim() : '';
|
|
5365
|
-
const content = typeof req.body?.content === 'string' ? req.body.content.trim().slice(0, 20_000) : '';
|
|
5366
|
-
const original = collaborationStore.getMessage(messageId);
|
|
5367
|
-
if (!sourceSessionId || !original || original.toSessionId !== sourceSessionId) {
|
|
5368
|
-
return res.status(404).json({ error: '协作消息不存在或不属于当前会话' });
|
|
5369
|
-
}
|
|
5370
|
-
if (!original.fromSessionId)
|
|
5371
|
-
return res.status(400).json({ error: '这条用户消息无需回复到其他会话' });
|
|
5372
|
-
try {
|
|
5373
|
-
collaborationStore.markRead([original.id]);
|
|
5374
|
-
const messages = collaborationStore.send({
|
|
5375
|
-
groupId: original.groupId,
|
|
5376
|
-
fromSessionId: sourceSessionId,
|
|
5377
|
-
toSessionIds: [original.fromSessionId],
|
|
5378
|
-
kind: 'reply',
|
|
5379
|
-
content,
|
|
5380
|
-
threadId: original.threadId,
|
|
5381
|
-
replyTo: original.id,
|
|
5382
|
-
});
|
|
5383
|
-
res.json({ ok: true, messages, delivery: tryDeliverCollaborationInbox(original.fromSessionId) });
|
|
5384
|
-
}
|
|
5385
|
-
catch (error) {
|
|
5386
|
-
res.status(400).json({ error: getErrorMessage(error) });
|
|
5387
|
-
}
|
|
5388
|
-
});
|
|
5396
|
+
router.use('/operations/orchestration', collaborationRoutes({ store: collaborationStore, resolveSession: resolveFrontendSessionId, deliver: tryDeliverCollaborationInbox }));
|
|
5389
5397
|
router.get('/operations/session-search', (req, res) => {
|
|
5390
5398
|
const query = typeof req.query.q === 'string' ? req.query.q : '';
|
|
5391
5399
|
const results = sessionSearchStore.search(query, Number(req.query.limit) || 30).map((result) => {
|
|
@@ -7158,6 +7166,8 @@ export function handleTerminalWebSocket(ws, sessionId, clientId, options = {}, i
|
|
|
7158
7166
|
type: 'connected',
|
|
7159
7167
|
streamEpoch: TERMINAL_STREAM_EPOCH,
|
|
7160
7168
|
outputProtocol: options.flowControl ? 2 : undefined,
|
|
7169
|
+
cols: session.cols,
|
|
7170
|
+
rows: session.rows,
|
|
7161
7171
|
runtime,
|
|
7162
7172
|
ptyBackend: session.ptyBackend || 'unknown',
|
|
7163
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()) {
|