termdock 1.4.296 → 1.4.297
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 +35 -2
- package/README.en.md +257 -0
- package/README.md +284 -133
- package/dist/client/assets/App-BbqtNY2x.js +177 -0
- package/dist/client/assets/App-BbqtNY2x.js.br +0 -0
- package/dist/client/assets/App-BbqtNY2x.js.gz +0 -0
- package/dist/client/assets/{ChangeWalkthroughPanel-DXprT-7m.js → ChangeWalkthroughPanel-CvQJIp_C.js} +1 -1
- package/dist/client/assets/ChangeWalkthroughPanel-CvQJIp_C.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-CvQJIp_C.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-B0OgRtyR.js +1 -0
- package/dist/client/assets/CollaborationMainWorkspace-B0OgRtyR.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-B0OgRtyR.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-C01GXOEq.js +2 -0
- package/dist/client/assets/ComputerControlView-CW1uA133.js +4 -0
- package/dist/client/assets/ComputerControlView-CW1uA133.js.br +0 -0
- package/dist/client/assets/ComputerControlView-CW1uA133.js.gz +0 -0
- package/dist/client/assets/{DagPlayground-DZcUunT5.js → DagPlayground-CdE3tQsl.js} +1 -1
- package/dist/client/assets/DagPlayground-CdE3tQsl.js.br +0 -0
- package/dist/client/assets/DagPlayground-CdE3tQsl.js.gz +0 -0
- package/dist/client/assets/{DiffLab-Brv4DCTb.js → DiffLab-BLGNnazd.js} +1 -1
- package/dist/client/assets/DiffLab-BLGNnazd.js.br +0 -0
- package/dist/client/assets/DiffLab-BLGNnazd.js.gz +0 -0
- package/dist/client/assets/{DiffReview-BuLu1qCi.js → DiffReview-CYw3ASI6.js} +1 -1
- package/dist/client/assets/DiffReview-CYw3ASI6.js.br +0 -0
- package/dist/client/assets/DiffReview-CYw3ASI6.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-DSGamuAD.js → DiffReviewLab-CySIuWq_.js} +1 -1
- package/dist/client/assets/DiffReviewLab-CySIuWq_.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-CySIuWq_.js.gz +0 -0
- package/dist/client/assets/{DiffViewer-BUCYxg27.js → DiffViewer-WsYvN75z.js} +13 -13
- package/dist/client/assets/DiffViewer-WsYvN75z.js.br +0 -0
- package/dist/client/assets/DiffViewer-WsYvN75z.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-Bnlo-oVE.js → ModelPreview-Dnlx9ByX.js} +1 -1
- package/dist/client/assets/ModelPreview-Dnlx9ByX.js.br +0 -0
- package/dist/client/assets/ModelPreview-Dnlx9ByX.js.gz +0 -0
- package/dist/client/assets/RightSidebar-Cp5b_y0u.js +1678 -0
- package/dist/client/assets/RightSidebar-Cp5b_y0u.js.br +0 -0
- package/dist/client/assets/RightSidebar-Cp5b_y0u.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-K6Ppkrbt.js → abnfDiagram-VCTEODGH-CK1mGFRQ.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-CK1mGFRQ.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-CK1mGFRQ.js.gz +0 -0
- package/dist/client/assets/app-DfACMixk.js +35 -0
- package/dist/client/assets/app-DfACMixk.js.br +0 -0
- package/dist/client/assets/app-DfACMixk.js.gz +0 -0
- package/dist/client/assets/{arc-Co2OQqtH.js → arc-yZE1gqDl.js} +1 -1
- package/dist/client/assets/arc-yZE1gqDl.js.br +0 -0
- package/dist/client/assets/arc-yZE1gqDl.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-BQnl7tVF.js → architectureDiagram-5GKGNRK7-U5XSC7cY.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-U5XSC7cY.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-U5XSC7cY.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-Bm5D5-qq.js → blockDiagram-I7D4REHJ-pVJnntSG.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-pVJnntSG.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-pVJnntSG.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-B9oPpey8.js → c4Diagram-7LVT6UL2-Bwz0AJJV.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-Bwz0AJJV.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-Bwz0AJJV.js.gz +0 -0
- package/dist/client/assets/channel-qqjDJtcZ.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-TMaWHH0U.js → chunk-2Q5K7J3B-CXJ1kP4X.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-CH7XpknV.js → chunk-5VM5RSS4-Dpdum_2z.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-CJ1rotBe.js → chunk-F27PBJKO-BCG5Zi_-.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-BCG5Zi_-.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-BCG5Zi_-.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-UYKZ5uOG.js → chunk-IMKFNOWR-BM-GcNcv.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-BM-GcNcv.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-BM-GcNcv.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-BYL8aXjQ.js → chunk-JWPE2WC7-BWiXpQT9.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-C3E8yfU2.js → chunk-POPQ4Y6H-vQSslMNv.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-DCTDYa1E.js → chunk-SVP7TREG-Aqfz6nPA.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-Aqfz6nPA.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-Aqfz6nPA.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-wHiaOsBh.js → chunk-TICWLB2K-DDjekMd0.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-DDjekMd0.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-DDjekMd0.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-D_d479hK.js → chunk-XXDRQBXY-BegIBcFg.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-BDuBrfNz.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-BDuBrfNz.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-DnhlPBLr.js → cose-bilkent-JH36ORCC-B2izFsBY.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-B2izFsBY.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-B2izFsBY.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-BsjzNqJZ.js → cynefin-OW5HDTMX-C7HA99Zc.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-C7HA99Zc.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-C7HA99Zc.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-sVUb0zY5.js → cynefinDiagram-5FMLGOSQ-vNIzcCYl.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-vNIzcCYl.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-vNIzcCYl.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-YErk8S-c.js → dagre-GXQ25YYZ-Bv9WHNT9.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-Bv9WHNT9.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-Bv9WHNT9.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-BaHbV5ho.js → diagram-S7CK7UJ4-DFg5UNic.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-DFg5UNic.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DFg5UNic.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-BocCYx3V.js → diagram-UQ7AKVKN-BFQsCAs7.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-BFQsCAs7.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BFQsCAs7.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-ZMTYwWOQ.js → diagram-VSXAHHWV-C4R8TKEv.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-C4R8TKEv.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-C4R8TKEv.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-xmk_EY1h.js → diagram-VX7I27RA-CL75HIf0.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-CL75HIf0.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CL75HIf0.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-DKDY_cG6.js → diagram-Z3DM3KII-CfJampuO.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-CfJampuO.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-CfJampuO.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-C_0z-Jx9.js → ebnfDiagram-PWID7BFC-Ct53jhNd.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-Ct53jhNd.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-Ct53jhNd.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-DHUaG7mt.js → erDiagram-RLTQ6QDP-BgL2xOOa.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-BgL2xOOa.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-BgL2xOOa.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-BMNsdMBD.js → flowDiagram-HODETNUW-Cp4Nu5jY.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-Cp4Nu5jY.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-Cp4Nu5jY.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-BH1AiKuF.js → ganttDiagram-EL5Y4UJY-DI0rdyPX.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-DI0rdyPX.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-DI0rdyPX.js.gz +0 -0
- package/dist/client/assets/{gestureArbiter-DvupkPra.js → gestureArbiter-Belsq4xq.js} +1 -1
- package/dist/client/assets/gestureArbiter-Belsq4xq.js.br +0 -0
- package/dist/client/assets/gestureArbiter-Belsq4xq.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-DAgV0MV-.js → gitGraphDiagram-WWUBYQGX-C0H4loQP.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-C0H4loQP.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-C0H4loQP.js.gz +0 -0
- package/dist/client/assets/index-CYmI1EsU.css +1 -0
- package/dist/client/assets/index-CYmI1EsU.css.br +0 -0
- package/dist/client/assets/index-CYmI1EsU.css.gz +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-C7iSg0cB.js → infoDiagram-27XIBGKW-BN4RpQTL.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-BpZwsQvw.js → ishikawaDiagram-5VMMS53U-BZQ5qZB_.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BZQ5qZB_.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BZQ5qZB_.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-DXEeFLvw.js → journeyDiagram-3NMN7TZE-Q8bx0_N4.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-Q8bx0_N4.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-Q8bx0_N4.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-CV9ivL5u.js → kanban-definition-UXKFOSKX-D7JhGtpO.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-D7JhGtpO.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-D7JhGtpO.js.gz +0 -0
- package/dist/client/assets/{layout-DsKKsJKX.js → layout-DERUg0xU.js} +1 -1
- package/dist/client/assets/layout-DERUg0xU.js.br +0 -0
- package/dist/client/assets/layout-DERUg0xU.js.gz +0 -0
- package/dist/client/assets/{linear-Dl9MDNV3.js → linear-lNTe4x00.js} +1 -1
- package/dist/client/assets/linear-lNTe4x00.js.br +0 -0
- package/dist/client/assets/linear-lNTe4x00.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-CxZFe3as.js → mermaid.core-WHCwLQDQ.js} +5 -5
- package/dist/client/assets/mermaid.core-WHCwLQDQ.js.br +0 -0
- package/dist/client/assets/mermaid.core-WHCwLQDQ.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-C3ormYdL.js → mindmap-definition-YA3MSWOX-BouapA4v.js} +1 -1
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BouapA4v.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BouapA4v.js.gz +0 -0
- package/dist/client/assets/{passwordBootstrap-a8z6jJNY.js → passwordBootstrap-E0piP814.js} +2 -2
- package/dist/client/assets/passwordBootstrap-E0piP814.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-E0piP814.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-CG0wNylH.js → pegDiagram-XKGWAZYB-Curpo5Rq.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-Curpo5Rq.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-Curpo5Rq.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-D2-ydqyX.js → pieDiagram-E7YTZNPT-BLQCHaOG.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-BLQCHaOG.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-BLQCHaOG.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-CK3fRC18.js → quadrantDiagram-AXDQQJYC-PViFqGao.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-PViFqGao.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-PViFqGao.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-V4EFlydu.js → railroadDiagram-O6MQD6OU-BtHo1LpT.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BtHo1LpT.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BtHo1LpT.js.gz +0 -0
- package/dist/client/assets/rdpSession-O_f9ejOI.js +1 -0
- package/dist/client/assets/rdpSession-O_f9ejOI.js.br +0 -0
- package/dist/client/assets/rdpSession-O_f9ejOI.js.gz +0 -0
- package/dist/client/assets/{referenceLongPress-BjmYd1vB.js → referenceLongPress-BZr7Lqzh.js} +1 -1
- package/dist/client/assets/referenceLongPress-BZr7Lqzh.js.br +0 -0
- package/dist/client/assets/referenceLongPress-BZr7Lqzh.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-Di1_tyAX.js → requirementDiagram-BXWQKSXE-BUm81HUU.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BUm81HUU.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BUm81HUU.js.gz +0 -0
- package/dist/client/assets/rfb-CxsXsMUX.js +5 -0
- package/dist/client/assets/rfb-CxsXsMUX.js.br +0 -0
- package/dist/client/assets/rfb-CxsXsMUX.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-D-qpWNza.js → sankeyDiagram-P5KCCOFB-B-_-aZ_y.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-B-_-aZ_y.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-B-_-aZ_y.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-DmIxafrB.js → sequenceDiagram-WJ2MYXX4-DT6LDIfx.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DT6LDIfx.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DT6LDIfx.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-4MTh3tTt.js → sizeCapture-INFHLROL-CTYerd9j.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-CTYerd9j.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CTYerd9j.js.gz +0 -0
- package/dist/client/assets/{sparkles-Dk93lHg0.js → sparkles-CgaUBIwH.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-CgYgneIv.js → stateDiagram-D77RDMKH-CQbb5x2_.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-CQbb5x2_.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-CQbb5x2_.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CJAVXcx1.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-OWA9HFVf.js → swimlanes-42K2YHIH-BHZ4X2Fd.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-BHZ4X2Fd.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-BHZ4X2Fd.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-VL2nyI27.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-BAzx5DyO.js → timeline-definition-24CTP7MA-BrjH6R6w.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-BrjH6R6w.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-BrjH6R6w.js.gz +0 -0
- package/dist/client/assets/{triangle-alert-CUIBOwqW.js → triangle-alert-C9IHD_eF.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-CBZ-PhTQ.js → vennDiagram-4TSXK5OY-C9f5jYgk.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-C9f5jYgk.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-C9f5jYgk.js.gz +0 -0
- package/dist/client/assets/{videoCompression-BCnqCkRK.js → videoCompression-DJaU8exd.js} +1 -1
- package/dist/client/assets/videoCompression-DJaU8exd.js.br +0 -0
- package/dist/client/assets/videoCompression-DJaU8exd.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-DbWzC6l5.js → wardleyDiagram-VM6X3IG4-6ljFvtiC.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-6ljFvtiC.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-6ljFvtiC.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-BmApnTWE.js → xychartDiagram-S5SC5T6Z-BaR2hm3e.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BaR2hm3e.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BaR2hm3e.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/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 +3 -3
- package/dist/server/agent/collaborationGroupRoutes.js +0 -2
- package/dist/server/agent/collaborationResultPresentation.js +14 -0
- package/dist/server/agent/collaborationService.js +3 -1
- package/dist/server/agent/collaborationStore.js +12 -21
- package/dist/server/agent/collaborationTaskBundles.js +2 -2
- package/dist/server/agent/collaborationTaskCli.js +4 -4
- package/dist/server/agent/collaborationTaskStore.js +87 -25
- package/dist/server/agent/collaborationTaskWorkspace.js +66 -1
- package/dist/server/agent/collaborationTeam.js +16 -0
- package/dist/server/agent/executionArchives.js +52 -0
- package/dist/server/entry.js +6 -1
- package/dist/server/federation/runtime.js +24 -6
- package/dist/server/routes/computer.js +198 -0
- package/dist/server/routes/computerTarget.js +23 -0
- package/dist/server/routes/guacamoleProtocol.js +55 -0
- package/dist/server/routes/rdp.js +162 -0
- package/dist/server/routes/terminal.js +375 -360
- package/dist/server/shared/computerPreferences.js +29 -0
- package/dist/server/utils/computerCredentials.js +88 -0
- package/dist/server/utils/sharedSampling.js +93 -0
- package/dist/server/utils/terminalOutputDelivery.js +8 -1
- package/dist/server/utils/tmuxMetadataWriter.js +61 -0
- package/package.json +4 -1
- package/runtime-manifest.json +4 -4
- package/dist/client/assets/App-BiLjlrk-.js +0 -171
- package/dist/client/assets/App-BiLjlrk-.js.br +0 -0
- package/dist/client/assets/App-BiLjlrk-.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DXprT-7m.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DXprT-7m.js.gz +0 -0
- package/dist/client/assets/DagPlayground-DZcUunT5.js.br +0 -0
- package/dist/client/assets/DagPlayground-DZcUunT5.js.gz +0 -0
- package/dist/client/assets/DiffLab-Brv4DCTb.js.br +0 -0
- package/dist/client/assets/DiffLab-Brv4DCTb.js.gz +0 -0
- package/dist/client/assets/DiffReview-BuLu1qCi.js.br +0 -0
- package/dist/client/assets/DiffReview-BuLu1qCi.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-DSGamuAD.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-DSGamuAD.js.gz +0 -0
- package/dist/client/assets/DiffViewer-BUCYxg27.js.br +0 -0
- package/dist/client/assets/DiffViewer-BUCYxg27.js.gz +0 -0
- package/dist/client/assets/ModelPreview-Bnlo-oVE.js.br +0 -0
- package/dist/client/assets/ModelPreview-Bnlo-oVE.js.gz +0 -0
- package/dist/client/assets/RightSidebar-r_3Lk5Rp.js +0 -1678
- package/dist/client/assets/RightSidebar-r_3Lk5Rp.js.br +0 -0
- package/dist/client/assets/RightSidebar-r_3Lk5Rp.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-K6Ppkrbt.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-K6Ppkrbt.js.gz +0 -0
- package/dist/client/assets/app-CATnTUZF.js +0 -35
- package/dist/client/assets/app-CATnTUZF.js.br +0 -0
- package/dist/client/assets/app-CATnTUZF.js.gz +0 -0
- package/dist/client/assets/arc-Co2OQqtH.js.br +0 -0
- package/dist/client/assets/arc-Co2OQqtH.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-BQnl7tVF.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-BQnl7tVF.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-Bm5D5-qq.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-Bm5D5-qq.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-B9oPpey8.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-B9oPpey8.js.gz +0 -0
- package/dist/client/assets/channel-Czv2W4qA.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-CJ1rotBe.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-CJ1rotBe.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-UYKZ5uOG.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-UYKZ5uOG.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-DCTDYa1E.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-DCTDYa1E.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-wHiaOsBh.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-wHiaOsBh.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-BdvOOL9k.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-BdvOOL9k.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-DnhlPBLr.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-DnhlPBLr.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-BsjzNqJZ.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-BsjzNqJZ.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-sVUb0zY5.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-sVUb0zY5.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-YErk8S-c.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-YErk8S-c.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-BaHbV5ho.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-BaHbV5ho.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BocCYx3V.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BocCYx3V.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-ZMTYwWOQ.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-ZMTYwWOQ.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-xmk_EY1h.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-xmk_EY1h.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-DKDY_cG6.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-DKDY_cG6.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-C_0z-Jx9.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-C_0z-Jx9.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DHUaG7mt.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DHUaG7mt.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-BMNsdMBD.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-BMNsdMBD.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-BH1AiKuF.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-BH1AiKuF.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-DvupkPra.js.br +0 -0
- package/dist/client/assets/gestureArbiter-DvupkPra.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DAgV0MV-.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DAgV0MV-.js.gz +0 -0
- package/dist/client/assets/index-Bi9A6QUP.css +0 -1
- package/dist/client/assets/index-Bi9A6QUP.css.br +0 -0
- package/dist/client/assets/index-Bi9A6QUP.css.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BpZwsQvw.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BpZwsQvw.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DXEeFLvw.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DXEeFLvw.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-CV9ivL5u.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-CV9ivL5u.js.gz +0 -0
- package/dist/client/assets/layout-DsKKsJKX.js.br +0 -0
- package/dist/client/assets/layout-DsKKsJKX.js.gz +0 -0
- package/dist/client/assets/linear-Dl9MDNV3.js.br +0 -0
- package/dist/client/assets/linear-Dl9MDNV3.js.gz +0 -0
- package/dist/client/assets/mermaid.core-CxZFe3as.js.br +0 -0
- package/dist/client/assets/mermaid.core-CxZFe3as.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-C3ormYdL.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-C3ormYdL.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-a8z6jJNY.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-a8z6jJNY.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-CG0wNylH.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-CG0wNylH.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-D2-ydqyX.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-D2-ydqyX.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-CK3fRC18.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-CK3fRC18.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-V4EFlydu.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-V4EFlydu.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-BjmYd1vB.js.br +0 -0
- package/dist/client/assets/referenceLongPress-BjmYd1vB.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-Di1_tyAX.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-Di1_tyAX.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-D-qpWNza.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-D-qpWNza.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DmIxafrB.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DmIxafrB.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-4MTh3tTt.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-4MTh3tTt.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-CgYgneIv.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-CgYgneIv.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CvPf0fxf.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-OWA9HFVf.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-OWA9HFVf.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-CZ_OauyW.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-BAzx5DyO.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-BAzx5DyO.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-CBZ-PhTQ.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-CBZ-PhTQ.js.gz +0 -0
- package/dist/client/assets/videoCompression-BCnqCkRK.js.br +0 -0
- package/dist/client/assets/videoCompression-BCnqCkRK.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DbWzC6l5.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DbWzC6l5.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BmApnTWE.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BmApnTWE.js.gz +0 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
3
|
+
import { hostname } from 'node:os';
|
|
4
|
+
import { Socket } from 'node:net';
|
|
5
|
+
import { resolveComputerHost } from './computerTarget.js';
|
|
6
|
+
import { handleRdpWebSocket, rdpBackendAvailable, stopRdpBridges } from './rdp.js';
|
|
7
|
+
import { computerLoginKey, computerPreferences } from '../shared/computerPreferences.js';
|
|
8
|
+
import { loadSettings, updateSettings } from '../utils/settings.js';
|
|
9
|
+
import { ComputerCredentialStore } from '../utils/computerCredentials.js';
|
|
10
|
+
export function createComputerRouter(read = loadSettings, update = updateSettings, credentials = new ComputerCredentialStore()) {
|
|
11
|
+
const router = Router();
|
|
12
|
+
router.get('/status', async (_req, res) => res.json({ platform: process.platform, hostname: hostname(), rdpAvailable: await rdpBackendAvailable() }));
|
|
13
|
+
router.get('/preferences', (_req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const saved = read().computerControl;
|
|
16
|
+
const preferences = computerPreferences(saved, process.platform);
|
|
17
|
+
let credentialKeys = [], credentialError = false;
|
|
18
|
+
try {
|
|
19
|
+
credentialKeys = [preferences.local, preferences.remote].filter(profile => profile.rememberLogin && credentials.has(profile)).map(computerLoginKey);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
credentialError = true;
|
|
23
|
+
}
|
|
24
|
+
res.set('Cache-Control', 'no-store').json({ preferences, configured: Boolean(saved), credentialKeys, credentialError });
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
res.status(500).json({ error: 'COMPUTER_PREFERENCES_READ_FAILED' });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
// The full-service encrypted API boundary applies to every credential route.
|
|
31
|
+
router.post('/credentials/:action', (req, res) => {
|
|
32
|
+
res.set('Cache-Control', 'no-store');
|
|
33
|
+
try {
|
|
34
|
+
const { profile, password } = req.body ?? {};
|
|
35
|
+
const preferences = computerPreferences(read().computerControl, process.platform);
|
|
36
|
+
const matched = [preferences.local, preferences.remote].find(value => isDeepStrictEqual(value, profile));
|
|
37
|
+
const action = req.params.action;
|
|
38
|
+
const allowedKeys = action === 'save' ? ['profile', 'password'] : ['profile'];
|
|
39
|
+
if (!matched || !['save', 'use', 'forget'].includes(action) || Object.keys(req.body).some(key => !allowedKeys.includes(key))) {
|
|
40
|
+
res.status(400).json({ error: 'INVALID_COMPUTER_CREDENTIALS' });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (action === 'forget') {
|
|
44
|
+
credentials.forget(matched);
|
|
45
|
+
res.json({ saved: false });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!matched.rememberLogin) {
|
|
49
|
+
res.status(400).json({ error: 'COMPUTER_LOGIN_NOT_REMEMBERED' });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (action === 'save') {
|
|
53
|
+
if (typeof password !== 'string' || !password || password.length > 1024 || password.includes('\0')) {
|
|
54
|
+
res.status(400).json({ error: 'INVALID_COMPUTER_CREDENTIALS' });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
credentials.save(matched, password);
|
|
58
|
+
res.json({ saved: true });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const secret = credentials.get(matched);
|
|
62
|
+
if (!secret) {
|
|
63
|
+
res.status(404).json({ error: 'COMPUTER_CREDENTIALS_MISSING' });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// Only the authenticated service page receives it, in memory, over Noise.
|
|
67
|
+
res.json({ password: secret });
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
res.status(500).json({ error: 'COMPUTER_CREDENTIALS_FAILED' });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
router.put('/preferences', (req, res) => {
|
|
74
|
+
const preferences = computerPreferences(req.body, process.platform);
|
|
75
|
+
// Strict projection prevents credentials and unknown fields entering settings.
|
|
76
|
+
if (!req.body || !isDeepStrictEqual(preferences, req.body)) {
|
|
77
|
+
res.status(400).json({ error: 'INVALID_COMPUTER_PREFERENCES' });
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
update((settings) => { settings.computerControl = preferences; });
|
|
82
|
+
res.json({ preferences });
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
res.status(500).json({ error: 'COMPUTER_PREFERENCES_WRITE_FAILED' });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
return router;
|
|
89
|
+
}
|
|
90
|
+
export default createComputerRouter();
|
|
91
|
+
const bridges = new Set();
|
|
92
|
+
export function stopComputerBridges() {
|
|
93
|
+
stopRdpBridges();
|
|
94
|
+
for (const bridge of bridges)
|
|
95
|
+
bridge.destroy();
|
|
96
|
+
bridges.clear();
|
|
97
|
+
}
|
|
98
|
+
/** RFB bytes are base64 records inside the existing authenticated Noise tunnel.
|
|
99
|
+
* The eight-record receive window applies TCP backpressure without dropping bytes. */
|
|
100
|
+
export function handleComputerWebSocket(socket, host, options) {
|
|
101
|
+
if (options?.protocol === 'rdp') {
|
|
102
|
+
handleRdpWebSocket(socket, host, Number(options.port ?? 3389));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (options?.protocol && options.protocol !== 'vnc') {
|
|
106
|
+
socket.close(4400, 'COMPUTER_INVALID_PROTOCOL');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
let closed = false;
|
|
110
|
+
let tcp;
|
|
111
|
+
let sent = 0;
|
|
112
|
+
let acked = 0;
|
|
113
|
+
let lastAckAt = Date.now();
|
|
114
|
+
let connected = false;
|
|
115
|
+
const finish = (code = 1000, reason = '') => {
|
|
116
|
+
if (closed)
|
|
117
|
+
return;
|
|
118
|
+
closed = true;
|
|
119
|
+
clearTimeout(connectTimer);
|
|
120
|
+
clearInterval(flowTimer);
|
|
121
|
+
if (tcp) {
|
|
122
|
+
bridges.delete(tcp);
|
|
123
|
+
tcp.destroy();
|
|
124
|
+
}
|
|
125
|
+
socket.close(code, reason);
|
|
126
|
+
};
|
|
127
|
+
const connectTimer = setTimeout(() => finish(4408, 'COMPUTER_CONNECT_TIMEOUT'), 10_000);
|
|
128
|
+
const flowTimer = setInterval(() => {
|
|
129
|
+
if (sent > acked && Date.now() - lastAckAt > 30_000)
|
|
130
|
+
finish(4408, 'COMPUTER_STREAM_TIMEOUT');
|
|
131
|
+
}, 5000);
|
|
132
|
+
const pump = () => {
|
|
133
|
+
if (closed || !tcp)
|
|
134
|
+
return;
|
|
135
|
+
while (sent - acked < 8 && tcp.readableLength > 0) {
|
|
136
|
+
const bytes = tcp.read(Math.min(tcp.readableLength, 32 * 1024));
|
|
137
|
+
if (!bytes)
|
|
138
|
+
break;
|
|
139
|
+
if (sent === acked)
|
|
140
|
+
lastAckAt = Date.now();
|
|
141
|
+
socket.send(JSON.stringify({ type: 'data', seq: ++sent, data: bytes.toString('base64') }));
|
|
142
|
+
if (closed)
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
socket.on('close', () => finish());
|
|
147
|
+
socket.on('error', () => finish());
|
|
148
|
+
socket.on('message', raw => {
|
|
149
|
+
if (closed)
|
|
150
|
+
return;
|
|
151
|
+
try {
|
|
152
|
+
const message = JSON.parse(raw.toString());
|
|
153
|
+
if (message.type === 'ack') {
|
|
154
|
+
if (!Number.isSafeInteger(message.seq) || message.seq <= acked || message.seq > sent)
|
|
155
|
+
throw new Error('INVALID_ACK');
|
|
156
|
+
acked = message.seq;
|
|
157
|
+
lastAckAt = Date.now();
|
|
158
|
+
pump();
|
|
159
|
+
}
|
|
160
|
+
else if (message.type === 'data') {
|
|
161
|
+
if (!connected || !tcp || typeof message.data !== 'string' || message.data.length > 88_000
|
|
162
|
+
|| !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(message.data))
|
|
163
|
+
throw new Error('INVALID_DATA');
|
|
164
|
+
const bytes = Buffer.from(message.data, 'base64');
|
|
165
|
+
if (!bytes.length || bytes.length > 64 * 1024 || tcp.writableLength + bytes.length > 1024 * 1024)
|
|
166
|
+
throw new Error('INPUT_LIMIT');
|
|
167
|
+
tcp.write(bytes);
|
|
168
|
+
}
|
|
169
|
+
else
|
|
170
|
+
throw new Error('INVALID_MESSAGE');
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
finish(4400, 'COMPUTER_INVALID_MESSAGE');
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
void (async () => {
|
|
177
|
+
try {
|
|
178
|
+
const address = await resolveComputerHost(host);
|
|
179
|
+
if (closed)
|
|
180
|
+
return;
|
|
181
|
+
tcp = new Socket();
|
|
182
|
+
bridges.add(tcp);
|
|
183
|
+
tcp.setNoDelay(true);
|
|
184
|
+
tcp.on('readable', pump);
|
|
185
|
+
tcp.on('connect', () => { connected = true; clearTimeout(connectTimer); });
|
|
186
|
+
tcp.on('end', () => finish(1000, 'COMPUTER_DISCONNECTED'));
|
|
187
|
+
tcp.on('close', () => finish(1000, 'COMPUTER_DISCONNECTED'));
|
|
188
|
+
tcp.on('error', error => {
|
|
189
|
+
const code = error.code;
|
|
190
|
+
finish(4502, code === 'ECONNREFUSED' ? 'COMPUTER_SCREEN_SHARING_OFF' : 'COMPUTER_UNREACHABLE');
|
|
191
|
+
});
|
|
192
|
+
tcp.connect({ host: address.address, family: address.family, port: 5900 });
|
|
193
|
+
}
|
|
194
|
+
catch (reason) {
|
|
195
|
+
finish(4502, reason instanceof Error && /^COMPUTER_(?:INVALID_HOST|PRIVATE_HOST_ONLY)$/.test(reason.message) ? reason.message : 'COMPUTER_UNREACHABLE');
|
|
196
|
+
}
|
|
197
|
+
})();
|
|
198
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { lookup } from 'node:dns/promises';
|
|
2
|
+
import { isIP } from 'node:net';
|
|
3
|
+
export function privateComputerAddress(address) {
|
|
4
|
+
if (isIP(address) === 4) {
|
|
5
|
+
const [a, b] = address.split('.').map(Number);
|
|
6
|
+
return a === 127 || a === 10 || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168);
|
|
7
|
+
}
|
|
8
|
+
const value = address.toLowerCase();
|
|
9
|
+
if (value.startsWith('::ffff:'))
|
|
10
|
+
return privateComputerAddress(value.slice(7));
|
|
11
|
+
return isIP(value) === 6 && (value === '::1' || value.startsWith('fc') || value.startsWith('fd'));
|
|
12
|
+
}
|
|
13
|
+
/** Resolve once, reject mixed public/private DNS answers, and dial the pinned IP. */
|
|
14
|
+
export async function resolveComputerHost(host) {
|
|
15
|
+
host = host.trim();
|
|
16
|
+
if (!host || host.length > 253 || (!isIP(host) && !/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/i.test(host))) {
|
|
17
|
+
throw new Error('COMPUTER_INVALID_HOST');
|
|
18
|
+
}
|
|
19
|
+
const addresses = await lookup(host, { all: true });
|
|
20
|
+
if (!addresses.length || addresses.some(item => !privateComputerAddress(item.address)))
|
|
21
|
+
throw new Error('COMPUTER_PRIVATE_HOST_ONLY');
|
|
22
|
+
return addresses[0];
|
|
23
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export function guacamoleInstruction(...values) {
|
|
2
|
+
return values.map(value => `${Array.from(value).length}.${value}`).join(',') + ';';
|
|
3
|
+
}
|
|
4
|
+
/** Incremental UTF-8-decoded Guacamole parser. Lengths count Unicode code points. */
|
|
5
|
+
export class GuacamoleParser {
|
|
6
|
+
instruction;
|
|
7
|
+
digits = '';
|
|
8
|
+
remaining = 0;
|
|
9
|
+
value = '';
|
|
10
|
+
values = [];
|
|
11
|
+
phase = 'length';
|
|
12
|
+
size = 0;
|
|
13
|
+
constructor(instruction) {
|
|
14
|
+
this.instruction = instruction;
|
|
15
|
+
}
|
|
16
|
+
receive(chunk) {
|
|
17
|
+
for (const char of chunk) {
|
|
18
|
+
if (++this.size > 2 * 1024 * 1024)
|
|
19
|
+
throw new Error('GUAC_INSTRUCTION_LIMIT');
|
|
20
|
+
if (this.phase === 'length') {
|
|
21
|
+
if (/^[0-9]$/.test(char) && this.digits.length < 7)
|
|
22
|
+
this.digits += char;
|
|
23
|
+
else if (char === '.' && this.digits) {
|
|
24
|
+
this.remaining = Number(this.digits);
|
|
25
|
+
this.digits = '';
|
|
26
|
+
if (this.remaining > 1024 * 1024)
|
|
27
|
+
throw new Error('GUAC_ELEMENT_LIMIT');
|
|
28
|
+
this.phase = this.remaining ? 'value' : 'separator';
|
|
29
|
+
}
|
|
30
|
+
else
|
|
31
|
+
throw new Error('GUAC_INVALID_LENGTH');
|
|
32
|
+
}
|
|
33
|
+
else if (this.phase === 'value') {
|
|
34
|
+
this.value += char;
|
|
35
|
+
if (--this.remaining === 0)
|
|
36
|
+
this.phase = 'separator';
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
if (char !== ',' && char !== ';')
|
|
40
|
+
throw new Error('GUAC_INVALID_SEPARATOR');
|
|
41
|
+
this.values.push(this.value);
|
|
42
|
+
this.value = '';
|
|
43
|
+
this.phase = 'length';
|
|
44
|
+
if (this.values.length > 256)
|
|
45
|
+
throw new Error('GUAC_ARGUMENT_LIMIT');
|
|
46
|
+
if (char === ';') {
|
|
47
|
+
const values = this.values;
|
|
48
|
+
this.values = [];
|
|
49
|
+
this.size = 0;
|
|
50
|
+
this.instruction(values);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { Socket } from 'node:net';
|
|
2
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
3
|
+
import { resolveComputerHost } from './computerTarget.js';
|
|
4
|
+
import { GuacamoleParser, guacamoleInstruction } from './guacamoleProtocol.js';
|
|
5
|
+
const guacdPort = () => Number(process.env.TERMDOCK_GUACD_PORT) || 4822;
|
|
6
|
+
const bridges = new Set();
|
|
7
|
+
export function stopRdpBridges() { for (const tcp of bridges)
|
|
8
|
+
tcp.destroy(); bridges.clear(); }
|
|
9
|
+
export function rdpBackendAvailable() {
|
|
10
|
+
return new Promise(resolve => {
|
|
11
|
+
const tcp = new Socket();
|
|
12
|
+
const done = (available) => { tcp.destroy(); resolve(available); };
|
|
13
|
+
tcp.setTimeout(500, () => done(false));
|
|
14
|
+
tcp.once('error', () => done(false));
|
|
15
|
+
tcp.once('connect', () => done(true));
|
|
16
|
+
tcp.connect(guacdPort(), '127.0.0.1');
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/** Only this server builds guacd's handshake. Credentials arrive inside the
|
|
20
|
+
* authenticated encrypted logical socket, never query strings or logs. */
|
|
21
|
+
export function handleRdpWebSocket(socket, host, port) {
|
|
22
|
+
let closed = false;
|
|
23
|
+
let started = false;
|
|
24
|
+
let ready = false;
|
|
25
|
+
let tcp;
|
|
26
|
+
let sent = 0;
|
|
27
|
+
let acked = 0;
|
|
28
|
+
let lastAckAt = Date.now();
|
|
29
|
+
let credentials = {};
|
|
30
|
+
const finish = (code = 1000, reason = '') => {
|
|
31
|
+
if (closed)
|
|
32
|
+
return;
|
|
33
|
+
closed = true;
|
|
34
|
+
credentials = {};
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
clearInterval(flowTimer);
|
|
37
|
+
if (tcp) {
|
|
38
|
+
bridges.delete(tcp);
|
|
39
|
+
tcp.destroy();
|
|
40
|
+
}
|
|
41
|
+
socket.close(code, reason);
|
|
42
|
+
};
|
|
43
|
+
const timer = setTimeout(() => finish(4408, 'COMPUTER_CONNECT_TIMEOUT'), 15_000);
|
|
44
|
+
const flowTimer = setInterval(() => {
|
|
45
|
+
if (sent > acked && Date.now() - lastAckAt > 30_000)
|
|
46
|
+
finish(4408, 'COMPUTER_STREAM_TIMEOUT');
|
|
47
|
+
}, 5000);
|
|
48
|
+
const decoder = new StringDecoder('utf8');
|
|
49
|
+
let instructions = [];
|
|
50
|
+
let handshakeSent = false;
|
|
51
|
+
const parser = new GuacamoleParser(values => {
|
|
52
|
+
const [opcode, ...args] = values;
|
|
53
|
+
if (!ready && opcode === 'args' && !handshakeSent) {
|
|
54
|
+
handshakeSent = true;
|
|
55
|
+
tcp.write(guacamoleInstruction('size', credentials.width, credentials.height, '96')
|
|
56
|
+
+ guacamoleInstruction('audio') + guacamoleInstruction('video')
|
|
57
|
+
+ guacamoleInstruction('image', 'image/png', 'image/jpeg')
|
|
58
|
+
+ guacamoleInstruction('connect', ...args.map(name => name.startsWith('VERSION_') ? 'VERSION_1_5_0' : credentials[name] ?? '')));
|
|
59
|
+
credentials = {};
|
|
60
|
+
}
|
|
61
|
+
else if (opcode === 'ready') {
|
|
62
|
+
if (!handshakeSent || ready)
|
|
63
|
+
throw new Error('GUAC_INVALID_HANDSHAKE');
|
|
64
|
+
ready = true;
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
instructions.push(values);
|
|
67
|
+
}
|
|
68
|
+
else if (ready || opcode === 'error')
|
|
69
|
+
instructions.push(values);
|
|
70
|
+
else
|
|
71
|
+
throw new Error('GUAC_INVALID_HANDSHAKE');
|
|
72
|
+
});
|
|
73
|
+
const pump = () => {
|
|
74
|
+
if (closed || !tcp)
|
|
75
|
+
return;
|
|
76
|
+
try {
|
|
77
|
+
while (sent - acked < 8 && tcp.readableLength > 0) {
|
|
78
|
+
const bytes = tcp.read(Math.min(tcp.readableLength, 32 * 1024));
|
|
79
|
+
if (!bytes)
|
|
80
|
+
break;
|
|
81
|
+
parser.receive(decoder.write(bytes));
|
|
82
|
+
if (instructions.length) {
|
|
83
|
+
if (sent === acked)
|
|
84
|
+
lastAckAt = Date.now();
|
|
85
|
+
socket.send(JSON.stringify({ type: 'instructions', seq: ++sent, instructions }));
|
|
86
|
+
instructions = [];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
finish(4502, 'COMPUTER_INVALID_MESSAGE');
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
socket.on('close', () => finish());
|
|
95
|
+
socket.on('error', () => finish());
|
|
96
|
+
socket.on('message', raw => {
|
|
97
|
+
if (closed)
|
|
98
|
+
return;
|
|
99
|
+
try {
|
|
100
|
+
if (raw.toString().length > 128 * 1024)
|
|
101
|
+
throw new Error('INPUT_LIMIT');
|
|
102
|
+
const message = JSON.parse(raw.toString());
|
|
103
|
+
if (message.type === 'start' && !started) {
|
|
104
|
+
started = true;
|
|
105
|
+
for (const key of ['username', 'password', 'domain']) {
|
|
106
|
+
if (typeof message[key] !== 'string' || message[key].length > 1024 || message[key].includes('\0'))
|
|
107
|
+
throw new Error('INVALID_CREDENTIALS');
|
|
108
|
+
}
|
|
109
|
+
if (!message.username.trim() || !message.password || typeof message.ignoreCert !== 'boolean')
|
|
110
|
+
throw new Error('INVALID_CREDENTIALS');
|
|
111
|
+
if (!Number.isSafeInteger(port) || port < 1 || port > 65535)
|
|
112
|
+
throw new Error('INVALID_PORT');
|
|
113
|
+
credentials = {
|
|
114
|
+
username: message.username, password: message.password, domain: message.domain,
|
|
115
|
+
port: String(port), security: 'any', 'ignore-cert': String(message.ignoreCert),
|
|
116
|
+
width: String(Math.max(640, Math.min(2560, Number(message.width) || 1280))),
|
|
117
|
+
height: String(Math.max(480, Math.min(1600, Number(message.height) || 800))),
|
|
118
|
+
'color-depth': '32', 'disable-audio': 'true', 'enable-drive': 'false',
|
|
119
|
+
'disable-copy': 'true', 'server-layout': 'en-us-qwerty', 'resize-method': 'display-update',
|
|
120
|
+
};
|
|
121
|
+
void resolveComputerHost(host).then(address => {
|
|
122
|
+
if (closed)
|
|
123
|
+
return;
|
|
124
|
+
credentials.hostname = address.address;
|
|
125
|
+
tcp = new Socket();
|
|
126
|
+
bridges.add(tcp);
|
|
127
|
+
tcp.setNoDelay(true);
|
|
128
|
+
tcp.on('readable', pump);
|
|
129
|
+
tcp.once('connect', () => { tcp.write(guacamoleInstruction('select', 'rdp')); });
|
|
130
|
+
tcp.once('error', () => finish(4503, 'COMPUTER_RDP_BACKEND_UNAVAILABLE'));
|
|
131
|
+
tcp.once('end', () => finish(1000, 'COMPUTER_DISCONNECTED'));
|
|
132
|
+
tcp.once('close', () => finish(1000, 'COMPUTER_DISCONNECTED'));
|
|
133
|
+
tcp.connect(guacdPort(), '127.0.0.1');
|
|
134
|
+
}).catch(reason => finish(4400, reason instanceof Error && /^COMPUTER_(?:INVALID_HOST|PRIVATE_HOST_ONLY)$/.test(reason.message) ? reason.message : 'COMPUTER_UNREACHABLE'));
|
|
135
|
+
}
|
|
136
|
+
else if (message.type === 'ack') {
|
|
137
|
+
if (!Number.isSafeInteger(message.seq) || message.seq <= acked || message.seq > sent)
|
|
138
|
+
throw new Error('INVALID_ACK');
|
|
139
|
+
acked = message.seq;
|
|
140
|
+
lastAckAt = Date.now();
|
|
141
|
+
pump();
|
|
142
|
+
}
|
|
143
|
+
else if (message.type === 'instruction' && ready && tcp) {
|
|
144
|
+
// Disallow re-handshakes, filesystem, recording, arbitrary connection
|
|
145
|
+
// parameters, and other protocols through this full-service bridge.
|
|
146
|
+
if (!['sync', 'key', 'mouse', 'clipboard', 'blob', 'end', 'ack', 'nop', 'size', 'disconnect'].includes(message.opcode)
|
|
147
|
+
|| !Array.isArray(message.args) || message.args.length > 8
|
|
148
|
+
|| message.args.some((arg) => typeof arg !== 'string' || arg.length > 64 * 1024)
|
|
149
|
+
|| tcp.writableLength > 1024 * 1024)
|
|
150
|
+
throw new Error('INVALID_INSTRUCTION');
|
|
151
|
+
if (message.opcode === 'size' && (message.args.length !== 2 || message.args.some((value, index) => !/^\d{1,4}$/.test(value) || Number(value) < (index ? 480 : 640) || Number(value) > (index ? 1600 : 2560))))
|
|
152
|
+
throw new Error('INVALID_SIZE');
|
|
153
|
+
tcp.write(guacamoleInstruction(message.opcode, ...message.args));
|
|
154
|
+
}
|
|
155
|
+
else
|
|
156
|
+
throw new Error('INVALID_MESSAGE');
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
finish(4400, 'COMPUTER_INVALID_MESSAGE');
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|