termdock 1.4.356 → 1.4.358
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/README.md +22 -4
- package/dist/client/assets/AndroidMirrorView-COoPljXi.js +3 -0
- package/dist/client/assets/AndroidMirrorView-COoPljXi.js.br +0 -0
- package/dist/client/assets/AndroidMirrorView-COoPljXi.js.gz +0 -0
- package/dist/client/assets/App-7BA7Jt-V.js +178 -0
- package/dist/client/assets/App-7BA7Jt-V.js.br +0 -0
- package/dist/client/assets/App-7BA7Jt-V.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-B51eVL3I.js +9 -0
- package/dist/client/assets/ArchitecturePanel-B51eVL3I.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-B51eVL3I.js.gz +0 -0
- package/dist/client/assets/{ChangeWalkthroughPanel-DO_zJfZr.js → ChangeWalkthroughPanel-ZGMybE0m.js} +2 -2
- package/dist/client/assets/ChangeWalkthroughPanel-ZGMybE0m.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-ZGMybE0m.js.gz +0 -0
- package/dist/client/assets/{ChatRichContent-Cu1E1cJ8.js → ChatRichContent-KkN7aIGv.js} +29 -29
- package/dist/client/assets/ChatRichContent-KkN7aIGv.js.br +0 -0
- package/dist/client/assets/ChatRichContent-KkN7aIGv.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-BVeBgGMT.js +1 -0
- package/dist/client/assets/CollaborationMainWorkspace-BVeBgGMT.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-BVeBgGMT.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-C3Od6xQy.js +1 -0
- package/dist/client/assets/ComputerControlView-CnaeZBuh.js +6 -0
- package/dist/client/assets/ComputerControlView-CnaeZBuh.js.br +0 -0
- package/dist/client/assets/ComputerControlView-CnaeZBuh.js.gz +0 -0
- package/dist/client/assets/{DagPlayground-CPYwfryM.js → DagPlayground-E7wKGgTF.js} +1 -1
- package/dist/client/assets/DagPlayground-E7wKGgTF.js.br +0 -0
- package/dist/client/assets/DagPlayground-E7wKGgTF.js.gz +0 -0
- package/dist/client/assets/{DiffLab-ptw96Q_4.js → DiffLab-BMXY4mRl.js} +1 -1
- package/dist/client/assets/DiffLab-BMXY4mRl.js.br +0 -0
- package/dist/client/assets/DiffLab-BMXY4mRl.js.gz +0 -0
- package/dist/client/assets/DiffReview-DHi_V-SX.js +1 -0
- package/dist/client/assets/DiffReview-DHi_V-SX.js.br +0 -0
- package/dist/client/assets/DiffReview-DHi_V-SX.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-eEi3y-0G.js → DiffReviewLab-nhoosNZM.js} +1 -1
- package/dist/client/assets/DiffReviewLab-nhoosNZM.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-nhoosNZM.js.gz +0 -0
- package/dist/client/assets/{DiffViewer-DqcQxp4Q.js → DiffViewer-CyDc8WOB.js} +1 -1
- package/dist/client/assets/DiffViewer-CyDc8WOB.js.br +0 -0
- package/dist/client/assets/DiffViewer-CyDc8WOB.js.gz +0 -0
- package/dist/client/assets/{GitLoadingSkeleton-DYrDIDF1.js → GitLoadingSkeleton-RRbTeMuA.js} +1 -1
- package/dist/client/assets/GitLoadingSkeleton-RRbTeMuA.js.br +0 -0
- package/dist/client/assets/GitLoadingSkeleton-RRbTeMuA.js.gz +0 -0
- package/dist/client/assets/Markdown-DKQhs57e.css +1 -0
- package/dist/client/assets/Markdown-DKQhs57e.css.br +0 -0
- package/dist/client/assets/Markdown-DKQhs57e.css.gz +0 -0
- package/dist/client/assets/Markdown-Ip0FYCJ1.js +23 -0
- package/dist/client/assets/Markdown-Ip0FYCJ1.js.br +0 -0
- package/dist/client/assets/Markdown-Ip0FYCJ1.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-DjtHLlqP.js → ModelPreview-DuwBl4st.js} +3 -3
- package/dist/client/assets/ModelPreview-DuwBl4st.js.br +0 -0
- package/dist/client/assets/ModelPreview-DuwBl4st.js.gz +0 -0
- package/dist/client/assets/RecordingSaveDialog-Czkjuaeu.js +1 -0
- package/dist/client/assets/RecordingSaveDialog-Czkjuaeu.js.br +0 -0
- package/dist/client/assets/RecordingSaveDialog-Czkjuaeu.js.gz +0 -0
- package/dist/client/assets/RightSidebar-BvYh2_n2.js +1662 -0
- package/dist/client/assets/RightSidebar-BvYh2_n2.js.br +0 -0
- package/dist/client/assets/RightSidebar-BvYh2_n2.js.gz +0 -0
- package/dist/client/assets/SessionChatView-hFOVLdHC.js +13 -0
- package/dist/client/assets/SessionChatView-hFOVLdHC.js.br +0 -0
- package/dist/client/assets/SessionChatView-hFOVLdHC.js.gz +0 -0
- package/dist/client/assets/SessionResourceSidebar-D-VXNdmE.js +2 -0
- package/dist/client/assets/SessionResourceSidebar-D-VXNdmE.js.br +0 -0
- package/dist/client/assets/SessionResourceSidebar-D-VXNdmE.js.gz +0 -0
- package/dist/client/assets/TodoWorkspace-CCL-wZTu.js +1 -0
- package/dist/client/assets/TodoWorkspace-CCL-wZTu.js.br +0 -0
- package/dist/client/assets/TodoWorkspace-CCL-wZTu.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-Cvf_KWZ0.js → abnfDiagram-VCTEODGH-CdEUK_s_.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-CdEUK_s_.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-CdEUK_s_.js.gz +0 -0
- package/dist/client/assets/app-DZ6mCJiW.js +37 -0
- package/dist/client/assets/app-DZ6mCJiW.js.br +0 -0
- package/dist/client/assets/app-DZ6mCJiW.js.gz +0 -0
- package/dist/client/assets/{arc-UvdowQOf.js → arc-Dmbeqzc-.js} +1 -1
- package/dist/client/assets/arc-Dmbeqzc-.js.br +0 -0
- package/dist/client/assets/arc-Dmbeqzc-.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-DotsbCCA.js → architectureDiagram-5GKGNRK7-CZcdXQrP.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CZcdXQrP.js.br +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-DotsbCCA.js.gz → architectureDiagram-5GKGNRK7-CZcdXQrP.js.gz} +0 -0
- package/dist/client/assets/{arrow-down-FGzRy9D-.js → arrow-down-Ty3zwcgl.js} +1 -1
- package/dist/client/assets/{blockDiagram-I7D4REHJ-CoOfSG4k.js → blockDiagram-I7D4REHJ-D9jFhXcb.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-D9jFhXcb.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-D9jFhXcb.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-BAfjgs8B.js → c4Diagram-7LVT6UL2-c9o0rNTP.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-c9o0rNTP.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-c9o0rNTP.js.gz +0 -0
- package/dist/client/assets/channel-BzD84hIE.js +1 -0
- package/dist/client/assets/{chevron-left-xqARwD-7.js → chevron-left-CVjGQZf0.js} +1 -1
- package/dist/client/assets/{chunk-2Q5K7J3B-DiiQ7BvG.js → chunk-2Q5K7J3B-Bl_5wWZ4.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-CczSK8wW.js → chunk-5VM5RSS4-D9XQn27m.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-BI3Di4Ve.js → chunk-F27PBJKO-DWyskKWD.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-DWyskKWD.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-DWyskKWD.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-Cvvs1wmn.js → chunk-IMKFNOWR-DrWbExPc.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-DrWbExPc.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-DrWbExPc.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-lrJRD9WA.js → chunk-JWPE2WC7-B7O1zrh6.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-CnPC9D8B.js → chunk-POPQ4Y6H-D7TlizKK.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-BZfMhm64.js → chunk-SVP7TREG-DNK10F6v.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-DNK10F6v.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-DNK10F6v.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-DmkEaRK3.js → chunk-TICWLB2K-zVD9YIYY.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-zVD9YIYY.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-zVD9YIYY.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-BP9Lccpe.js → chunk-XXDRQBXY-bRScHSGz.js} +1 -1
- package/dist/client/assets/{circle-DGZOpG8V.js → circle-BIJQEbCa.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-DNwfONVW.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-DNwfONVW.js +1 -0
- package/dist/client/assets/{clipboard-rLx5CzbY.js → clipboard-DV8t7tww.js} +1 -1
- package/dist/client/assets/clipboardText-C4QKbGHg.css +1 -0
- package/dist/client/assets/clipboardText-C4QKbGHg.css.br +0 -0
- package/dist/client/assets/clipboardText-C4QKbGHg.css.gz +0 -0
- package/dist/client/assets/clipboardText-DoB9NjH8.js +1 -0
- package/dist/client/assets/clipboardText-DoB9NjH8.js.br +0 -0
- package/dist/client/assets/clipboardText-DoB9NjH8.js.gz +0 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-0xPYMc2z.js → cose-bilkent-JH36ORCC-Bf6IObhf.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-Bf6IObhf.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-Bf6IObhf.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-BQ7-lCHm.js → cynefin-OW5HDTMX-DAtjde93.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-DAtjde93.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-DAtjde93.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-CazHuSj1.js → cynefinDiagram-5FMLGOSQ-Bwoin99V.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Bwoin99V.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Bwoin99V.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-CJNgsyHQ.js → dagre-GXQ25YYZ-dxPzSob9.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-dxPzSob9.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-dxPzSob9.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-D4xXIj1P.js → diagram-S7CK7UJ4-BXdiLivz.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-BXdiLivz.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-BXdiLivz.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-BWhpL-35.js → diagram-UQ7AKVKN-DXveP3fg.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-DXveP3fg.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-DXveP3fg.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-DG9A7EgJ.js → diagram-VSXAHHWV-BAZtoQue.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-BAZtoQue.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-BAZtoQue.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-L5PSvynM.js → diagram-VX7I27RA-Bhc7cKpk.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-Bhc7cKpk.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-Bhc7cKpk.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-moBH7ruR.js → diagram-Z3DM3KII-DWIa_F-y.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-DWIa_F-y.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-DWIa_F-y.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-DEiGgcRP.js → ebnfDiagram-PWID7BFC-B3LDILSm.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-B3LDILSm.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-B3LDILSm.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-BboIleYi.js → erDiagram-RLTQ6QDP-Cfu95bMX.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-Cfu95bMX.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-Cfu95bMX.js.gz +0 -0
- package/dist/client/assets/{external-link-AZ3wjUg5.js → external-link-DodqgrCc.js} +1 -1
- package/dist/client/assets/{flowDiagram-HODETNUW-COxz7Rda.js → flowDiagram-HODETNUW-DVtO6aQE.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-DVtO6aQE.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-DVtO6aQE.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-_XdRvgyZ.js → ganttDiagram-EL5Y4UJY-Bh1TvYzb.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-Bh1TvYzb.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-Bh1TvYzb.js.gz +0 -0
- package/dist/client/assets/{git-compare-90v75Jal.js → git-compare-KkbucDhN.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-CBb9xBNW.js → gitGraphDiagram-WWUBYQGX-BKbRxPsF.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BKbRxPsF.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BKbRxPsF.js.gz +0 -0
- package/dist/client/assets/index-DEAMSl55.css +1 -0
- package/dist/client/assets/index-DEAMSl55.css.br +0 -0
- package/dist/client/assets/index-DEAMSl55.css.gz +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-DjLdeNZi.js → infoDiagram-27XIBGKW-CLhO8B16.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-Bcr-lhIE.js → ishikawaDiagram-5VMMS53U-BwchGTP9.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BwchGTP9.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BwchGTP9.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-3vK6s9Pq.js → journeyDiagram-3NMN7TZE-eWBpuyMm.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-eWBpuyMm.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-eWBpuyMm.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-DqDYDca0.js → kanban-definition-UXKFOSKX-Sik-RHYc.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-Sik-RHYc.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-Sik-RHYc.js.gz +0 -0
- package/dist/client/assets/{layout-C7_WwzCP.js → layout-BJX2CDWF.js} +1 -1
- package/dist/client/assets/layout-BJX2CDWF.js.br +0 -0
- package/dist/client/assets/layout-BJX2CDWF.js.gz +0 -0
- package/dist/client/assets/{linear-BQCRhl6P.js → linear-0FKHr918.js} +1 -1
- package/dist/client/assets/{linear-BQCRhl6P.js.br → linear-0FKHr918.js.br} +0 -0
- package/dist/client/assets/linear-0FKHr918.js.gz +0 -0
- package/dist/client/assets/{link-Cq14tNBm.js → link-DWUQqMDL.js} +3 -3
- package/dist/client/assets/link-DWUQqMDL.js.br +0 -0
- package/dist/client/assets/link-DWUQqMDL.js.gz +0 -0
- package/dist/client/assets/{list-tree-C281zrdg.js → list-tree-DxdLnXUB.js} +1 -1
- package/dist/client/assets/{maximize-2-CovPxWdw.js → maximize-2-Clx4ktqB.js} +1 -1
- package/dist/client/assets/{mermaid.core-DGsccxt-.js → mermaid.core-DWoaNsig.js} +5 -5
- package/dist/client/assets/mermaid.core-DWoaNsig.js.br +0 -0
- package/dist/client/assets/mermaid.core-DWoaNsig.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-l0eF1PQE.js → mindmap-definition-YA3MSWOX-kFqW8-1C.js} +1 -1
- package/dist/client/assets/mindmap-definition-YA3MSWOX-kFqW8-1C.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-kFqW8-1C.js.gz +0 -0
- package/dist/client/assets/{passwordBootstrap-B5VzheZz.js → passwordBootstrap-ZDVK8UPE.js} +1 -1
- package/dist/client/assets/passwordBootstrap-ZDVK8UPE.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-ZDVK8UPE.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-fRrxGIDM.js → pegDiagram-XKGWAZYB-CndGmUtZ.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-CndGmUtZ.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-CndGmUtZ.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-B-EBa3qN.js → pieDiagram-E7YTZNPT-Q550203T.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-Q550203T.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-Q550203T.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-Dj4e6f4h.js → quadrantDiagram-AXDQQJYC-BrjlUGzZ.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BrjlUGzZ.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BrjlUGzZ.js.gz +0 -0
- package/dist/client/assets/{quote-COQfrXhi.js → quote-B9G-yF_k.js} +1 -1
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-CEMppxTC.js → railroadDiagram-O6MQD6OU-CSqOaX_w.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-CSqOaX_w.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-CSqOaX_w.js.gz +0 -0
- package/dist/client/assets/rdpSession-DdqSmi-Z.js +1 -0
- package/dist/client/assets/rdpSession-DdqSmi-Z.js.br +0 -0
- package/dist/client/assets/rdpSession-DdqSmi-Z.js.gz +0 -0
- package/dist/client/assets/referenceSelection-0uCpJjt2.js +4 -0
- package/dist/client/assets/referenceSelection-0uCpJjt2.js.br +0 -0
- package/dist/client/assets/referenceSelection-0uCpJjt2.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-Dri1k4_x.js → requirementDiagram-BXWQKSXE-NGPth3Pf.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-NGPth3Pf.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-NGPth3Pf.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-CMsL_tYL.js → sankeyDiagram-P5KCCOFB-CbxpJ6iI.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CbxpJ6iI.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CbxpJ6iI.js.gz +0 -0
- package/dist/client/assets/{search-Dg6HWygj.js → search-DAQ_AOVP.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-4_4hZtOd.js → sequenceDiagram-WJ2MYXX4-D3gJOK25.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D3gJOK25.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D3gJOK25.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-3QA6qj2j.js → sizeCapture-INFHLROL-D4D1KI0n.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-D4D1KI0n.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-D4D1KI0n.js.gz +0 -0
- package/dist/client/assets/{stateDiagram-D77RDMKH-90IFPhD9.js → stateDiagram-D77RDMKH-BjtgjBg5.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-BjtgjBg5.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-BjtgjBg5.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-BkFsvhZ0.js +1 -0
- package/dist/client/assets/{sun-BAMMjiA8.js → sun-DvwMJkda.js} +1 -1
- package/dist/client/assets/{swimlanes-42K2YHIH-BuHieVWC.js → swimlanes-42K2YHIH-C17jwdFo.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-C17jwdFo.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-C17jwdFo.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-Bcw2hbFj.js +8 -0
- package/dist/client/assets/swiper-BaumVgFd.js +1 -0
- package/dist/client/assets/swiper-BaumVgFd.js.br +0 -0
- package/dist/client/assets/swiper-BaumVgFd.js.gz +0 -0
- package/dist/client/assets/{terminal-Dea8wJF3.js → terminal-DaiFCKy_.js} +7 -7
- package/dist/client/assets/terminal-DaiFCKy_.js.br +0 -0
- package/dist/client/assets/terminal-DaiFCKy_.js.gz +0 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-C7FpaNwf.js → timeline-definition-24CTP7MA-CtdHmPoC.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-CtdHmPoC.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-CtdHmPoC.js.gz +0 -0
- package/dist/client/assets/triangle-alert-D-tt7pA2.js +1 -0
- package/dist/client/assets/{unplug-DN2SNXUL.js → unplug-BQxOculv.js} +1 -1
- package/dist/client/assets/use-gesture-react.esm-CM42s15B.js +1 -0
- package/dist/client/assets/use-gesture-react.esm-CM42s15B.js.br +0 -0
- package/dist/client/assets/use-gesture-react.esm-CM42s15B.js.gz +0 -0
- package/dist/client/assets/{vennDiagram-4TSXK5OY-CxoHHVcs.js → vennDiagram-4TSXK5OY-DhwCFaVe.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-DhwCFaVe.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-DhwCFaVe.js.gz +0 -0
- package/dist/client/assets/{videoCompression-rr2lu3uy.js → videoCompression-BFhMaMl7.js} +1 -1
- package/dist/client/assets/videoCompression-BFhMaMl7.js.br +0 -0
- package/dist/client/assets/videoCompression-BFhMaMl7.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-B4Hsh-Ad.js → wardleyDiagram-VM6X3IG4-BP4Z-QQ4.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-BP4Z-QQ4.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-BP4Z-QQ4.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-CghzsxQv.js → xychartDiagram-S5SC5T6Z-CEPWf1tk.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-CEPWf1tk.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-CEPWf1tk.js.gz +0 -0
- package/dist/client/index.html +3 -3
- package/dist/client/index.html.br +0 -0
- package/dist/client/index.html.gz +0 -0
- package/dist/client/sw-encrypted-background.js +1 -1
- package/dist/client/sw-encrypted-background.js.br +0 -0
- package/dist/client/sw-encrypted-background.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/collaborationPeerTransport.js +14 -69
- package/dist/server/agent/collaborationStore.js +5 -0
- package/dist/server/agent/installers.js +89 -33
- package/dist/server/agent/integrationServer.js +31 -2
- package/dist/server/agent/pluginPackages.js +51 -22
- package/dist/server/agent/sessionChatRoutes.js +45 -7
- package/dist/server/agentHook.js +56 -12
- package/dist/server/android/adb.js +24 -31
- package/dist/server/cli.js +52 -110
- package/dist/server/entry.js +17 -23
- package/dist/server/federation/cli.js +13 -47
- package/dist/server/federation/connectionErrors.js +37 -0
- package/dist/server/federation/connectionModel.js +83 -0
- package/dist/server/federation/desktopTargets.js +35 -13
- package/dist/server/federation/directRoutes.js +28 -36
- package/dist/server/federation/directTargetRegistry.js +106 -0
- package/dist/server/federation/httpUploadLimit.js +8 -0
- package/dist/server/federation/relay.js +13 -6
- package/dist/server/federation/relayTransport.js +147 -0
- package/dist/server/federation/routeAccess.js +5 -3
- package/dist/server/federation/runtime.js +4 -2
- package/dist/server/federation/selfRelay.js +10 -59
- package/dist/server/federation/serviceTransport.js +123 -0
- package/dist/server/federation/streamLimits.js +3 -0
- package/dist/server/federation/trustedCa.js +61 -0
- package/dist/server/routes/android.js +5 -7
- package/dist/server/routes/terminal.js +149 -111
- package/dist/server/shared/androidDevices.js +64 -0
- package/dist/server/shared/programStatus.js +174 -0
- package/dist/server/shared/shareResources.js +3 -2
- package/dist/server/todos/routes.js +36 -0
- package/dist/server/todos/store.js +31 -1
- package/dist/server/utils/bootCheck.js +41 -271
- package/dist/server/utils/localHttpsSetup.js +257 -0
- package/dist/server/utils/oscBuffer.js +9 -0
- package/dist/server/utils/sessionDisplayName.js +11 -9
- package/dist/server/utils/settings.js +6 -3
- package/dist/server/utils/tmuxDisplayGeometry.js +54 -0
- package/dist/server/utils/tmuxObserverSize.js +26 -0
- package/docs/integrations.md +2 -1
- package/package.json +6 -1
- package/runtime-manifest.json +3 -3
- package/scripts/install-utils.cjs +81 -0
- package/scripts/postinstall.mjs +19 -106
- package/dist/client/assets/AndroidMirrorView-DW9oRr0w.js +0 -3
- package/dist/client/assets/AndroidMirrorView-DW9oRr0w.js.br +0 -0
- package/dist/client/assets/AndroidMirrorView-DW9oRr0w.js.gz +0 -0
- package/dist/client/assets/App-DEZ2i9ZZ.js +0 -178
- package/dist/client/assets/App-DEZ2i9ZZ.js.br +0 -0
- package/dist/client/assets/App-DEZ2i9ZZ.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-Bf4b-6An.js +0 -9
- package/dist/client/assets/ArchitecturePanel-Bf4b-6An.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-Bf4b-6An.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DO_zJfZr.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DO_zJfZr.js.gz +0 -0
- package/dist/client/assets/ChatRichContent-Cu1E1cJ8.js.br +0 -0
- package/dist/client/assets/ChatRichContent-Cu1E1cJ8.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-BhmpSEbf.js +0 -1
- package/dist/client/assets/CollaborationMainWorkspace-BhmpSEbf.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-BhmpSEbf.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-CX0Aqdi2.js +0 -2
- package/dist/client/assets/ComputerControlView-DjQLzn10.js +0 -6
- package/dist/client/assets/ComputerControlView-DjQLzn10.js.br +0 -0
- package/dist/client/assets/ComputerControlView-DjQLzn10.js.gz +0 -0
- package/dist/client/assets/DagPlayground-CPYwfryM.js.br +0 -0
- package/dist/client/assets/DagPlayground-CPYwfryM.js.gz +0 -0
- package/dist/client/assets/DiffLab-ptw96Q_4.js.br +0 -0
- package/dist/client/assets/DiffLab-ptw96Q_4.js.gz +0 -0
- package/dist/client/assets/DiffReview-12E2zWyL.js +0 -1
- package/dist/client/assets/DiffReview-12E2zWyL.js.br +0 -0
- package/dist/client/assets/DiffReview-12E2zWyL.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-eEi3y-0G.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-eEi3y-0G.js.gz +0 -0
- package/dist/client/assets/DiffViewer-DqcQxp4Q.js.br +0 -0
- package/dist/client/assets/DiffViewer-DqcQxp4Q.js.gz +0 -0
- package/dist/client/assets/GitLoadingSkeleton-DYrDIDF1.js.br +0 -0
- package/dist/client/assets/GitLoadingSkeleton-DYrDIDF1.js.gz +0 -0
- package/dist/client/assets/MarkdownPreview-CsJGsDRL.js +0 -19
- package/dist/client/assets/MarkdownPreview-CsJGsDRL.js.br +0 -0
- package/dist/client/assets/MarkdownPreview-CsJGsDRL.js.gz +0 -0
- package/dist/client/assets/ModelPreview-DjtHLlqP.js.br +0 -0
- package/dist/client/assets/ModelPreview-DjtHLlqP.js.gz +0 -0
- package/dist/client/assets/RecordingSaveDialog-DUtwgIME.js +0 -1
- package/dist/client/assets/RecordingSaveDialog-DUtwgIME.js.br +0 -0
- package/dist/client/assets/RecordingSaveDialog-DUtwgIME.js.gz +0 -0
- package/dist/client/assets/RightSidebar-BltjUFwI.js +0 -1662
- package/dist/client/assets/RightSidebar-BltjUFwI.js.br +0 -0
- package/dist/client/assets/RightSidebar-BltjUFwI.js.gz +0 -0
- package/dist/client/assets/SessionChatView-CdKfHRp8.js +0 -13
- package/dist/client/assets/SessionChatView-CdKfHRp8.js.br +0 -0
- package/dist/client/assets/SessionChatView-CdKfHRp8.js.gz +0 -0
- package/dist/client/assets/SessionChatView-D8GoESjb.css +0 -1
- package/dist/client/assets/SessionChatView-D8GoESjb.css.br +0 -0
- package/dist/client/assets/SessionChatView-D8GoESjb.css.gz +0 -0
- package/dist/client/assets/SessionResourceSidebar-DmQOydhf.js +0 -2
- package/dist/client/assets/SessionResourceSidebar-DmQOydhf.js.br +0 -0
- package/dist/client/assets/SessionResourceSidebar-DmQOydhf.js.gz +0 -0
- package/dist/client/assets/TodoWorkspace-Bh1C035B.js +0 -1
- package/dist/client/assets/TodoWorkspace-Bh1C035B.js.br +0 -0
- package/dist/client/assets/TodoWorkspace-Bh1C035B.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-Cvf_KWZ0.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-Cvf_KWZ0.js.gz +0 -0
- package/dist/client/assets/app-DuUnqxq-.js +0 -37
- package/dist/client/assets/app-DuUnqxq-.js.br +0 -0
- package/dist/client/assets/app-DuUnqxq-.js.gz +0 -0
- package/dist/client/assets/arc-UvdowQOf.js.br +0 -0
- package/dist/client/assets/arc-UvdowQOf.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DotsbCCA.js.br +0 -0
- package/dist/client/assets/arrow-up-DNkfesW0.js +0 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-CoOfSG4k.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-CoOfSG4k.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-BAfjgs8B.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-BAfjgs8B.js.gz +0 -0
- package/dist/client/assets/channel-BzMU9Wks.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-BI3Di4Ve.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-BI3Di4Ve.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-Cvvs1wmn.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-Cvvs1wmn.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-BZfMhm64.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-BZfMhm64.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-DmkEaRK3.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-DmkEaRK3.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-DtAtEoWU.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-DtAtEoWU.js +0 -1
- package/dist/client/assets/clipboardText-cZXParp1.js +0 -1
- package/dist/client/assets/clipboardText-cZXParp1.js.br +0 -0
- package/dist/client/assets/clipboardText-cZXParp1.js.gz +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-0xPYMc2z.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-0xPYMc2z.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-BQ7-lCHm.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-BQ7-lCHm.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CazHuSj1.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CazHuSj1.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CJNgsyHQ.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CJNgsyHQ.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-D4xXIj1P.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-D4xXIj1P.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BWhpL-35.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BWhpL-35.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-DG9A7EgJ.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-DG9A7EgJ.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-L5PSvynM.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-L5PSvynM.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-moBH7ruR.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-moBH7ruR.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-DEiGgcRP.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-DEiGgcRP.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-BboIleYi.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-BboIleYi.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-COxz7Rda.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-COxz7Rda.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-_XdRvgyZ.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-_XdRvgyZ.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CBb9xBNW.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CBb9xBNW.js.gz +0 -0
- package/dist/client/assets/index-nIgxJ6I3.css +0 -1
- package/dist/client/assets/index-nIgxJ6I3.css.br +0 -0
- package/dist/client/assets/index-nIgxJ6I3.css.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-Bcr-lhIE.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-Bcr-lhIE.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-3vK6s9Pq.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-3vK6s9Pq.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-DqDYDca0.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-DqDYDca0.js.gz +0 -0
- package/dist/client/assets/layout-C7_WwzCP.js.br +0 -0
- package/dist/client/assets/layout-C7_WwzCP.js.gz +0 -0
- package/dist/client/assets/linear-BQCRhl6P.js.gz +0 -0
- package/dist/client/assets/link-Cq14tNBm.js.br +0 -0
- package/dist/client/assets/link-Cq14tNBm.js.gz +0 -0
- package/dist/client/assets/loader-circle-Df0zpx5N.js +0 -1
- package/dist/client/assets/mermaid.core-DGsccxt-.js.br +0 -0
- package/dist/client/assets/mermaid.core-DGsccxt-.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-l0eF1PQE.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-l0eF1PQE.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-B5VzheZz.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-B5VzheZz.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-fRrxGIDM.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-fRrxGIDM.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-B-EBa3qN.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-B-EBa3qN.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-Dj4e6f4h.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-Dj4e6f4h.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-CEMppxTC.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-CEMppxTC.js.gz +0 -0
- package/dist/client/assets/rdpSession-C5rS-ImV.js +0 -1
- package/dist/client/assets/rdpSession-C5rS-ImV.js.br +0 -0
- package/dist/client/assets/rdpSession-C5rS-ImV.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-Dri1k4_x.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-Dri1k4_x.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CMsL_tYL.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CMsL_tYL.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-4_4hZtOd.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-4_4hZtOd.js.gz +0 -0
- package/dist/client/assets/settings-2-iGqzPez0.js +0 -1
- package/dist/client/assets/sizeCapture-INFHLROL-3QA6qj2j.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-3QA6qj2j.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-90IFPhD9.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-90IFPhD9.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CH-yvmL4.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-BuHieVWC.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-BuHieVWC.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-P_rDqbtg.js +0 -8
- package/dist/client/assets/swiper-DJjq1TYT.js +0 -1
- package/dist/client/assets/swiper-DJjq1TYT.js.br +0 -0
- package/dist/client/assets/swiper-DJjq1TYT.js.gz +0 -0
- package/dist/client/assets/syntaxHighlight-Cz6TPDW9.js +0 -4
- package/dist/client/assets/syntaxHighlight-Cz6TPDW9.js.br +0 -0
- package/dist/client/assets/syntaxHighlight-Cz6TPDW9.js.gz +0 -0
- package/dist/client/assets/terminal-Dea8wJF3.js.br +0 -0
- package/dist/client/assets/terminal-Dea8wJF3.js.gz +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-C7FpaNwf.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-C7FpaNwf.js.gz +0 -0
- package/dist/client/assets/triangle-alert-Oj-OsZDw.js +0 -1
- package/dist/client/assets/use-gesture-react.esm-DYpdiE_k.js +0 -1
- package/dist/client/assets/use-gesture-react.esm-DYpdiE_k.js.br +0 -0
- package/dist/client/assets/use-gesture-react.esm-DYpdiE_k.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-CxoHHVcs.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-CxoHHVcs.js.gz +0 -0
- package/dist/client/assets/videoCompression-rr2lu3uy.js.br +0 -0
- package/dist/client/assets/videoCompression-rr2lu3uy.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-B4Hsh-Ad.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-B4Hsh-Ad.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-CghzsxQv.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-CghzsxQv.js.gz +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { RELAY_CLIENT_CONNECT_TIMEOUT_MS, RELAY_MAX_FRAME_BYTES, RELAY_MAX_BUFFERED_BYTES } from './streamLimits.js';
|
|
2
|
+
import { fromBase64, toBase64 } from './packets.js';
|
|
3
|
+
import { ConnectionAccessError } from './connectionErrors.js';
|
|
4
|
+
const MAX_FRAME_BYTES = RELAY_MAX_FRAME_BYTES;
|
|
5
|
+
const MAX_BUFFER_BYTES = RELAY_MAX_BUFFERED_BYTES;
|
|
6
|
+
/** A byte WebSocket facade over a single explicitly addressed relay stream. */
|
|
7
|
+
export class RelayTransport extends EventTarget {
|
|
8
|
+
url;
|
|
9
|
+
CONNECTING = 0;
|
|
10
|
+
OPEN = 1;
|
|
11
|
+
CLOSING = 2;
|
|
12
|
+
CLOSED = 3;
|
|
13
|
+
protocol = '';
|
|
14
|
+
extensions = '';
|
|
15
|
+
binaryType = 'arraybuffer';
|
|
16
|
+
readyState = 0;
|
|
17
|
+
transportFailure;
|
|
18
|
+
onopen = null;
|
|
19
|
+
onmessage = null;
|
|
20
|
+
onerror = null;
|
|
21
|
+
onclose = null;
|
|
22
|
+
connectionTimings = { entryOpenMs: null, routeOpenMs: null };
|
|
23
|
+
upstream;
|
|
24
|
+
streamId = crypto.randomUUID();
|
|
25
|
+
requested = false;
|
|
26
|
+
deadline;
|
|
27
|
+
get bufferedAmount() { return this.upstream.bufferedAmount; }
|
|
28
|
+
constructor(url, targetServiceId, socketFactory, context = {}) {
|
|
29
|
+
super();
|
|
30
|
+
this.url = url;
|
|
31
|
+
if (!/^[a-zA-Z0-9_.:-]{1,160}$/.test(targetServiceId))
|
|
32
|
+
throw new Error('Invalid target identity');
|
|
33
|
+
if (context.entryServiceId === targetServiceId)
|
|
34
|
+
throw new ConnectionAccessError('RELAY_SELF_TARGET', { ...context, targetServiceId });
|
|
35
|
+
const started = performance.now();
|
|
36
|
+
let entryReadyAt;
|
|
37
|
+
this.upstream = socketFactory(url);
|
|
38
|
+
this.upstream.addEventListener('open', () => {
|
|
39
|
+
this.connectionTimings.entryOpenMs = performance.now() - started;
|
|
40
|
+
});
|
|
41
|
+
this.deadline = setTimeout(() => this.fail('Relay connection timed out'), RELAY_CLIENT_CONNECT_TIMEOUT_MS);
|
|
42
|
+
this.upstream.addEventListener('message', event => {
|
|
43
|
+
if (this.readyState === 3)
|
|
44
|
+
return;
|
|
45
|
+
try {
|
|
46
|
+
if (typeof event.data !== 'string' || new TextEncoder().encode(event.data).length > MAX_FRAME_BYTES)
|
|
47
|
+
throw new Error('Invalid relay envelope');
|
|
48
|
+
const frame = JSON.parse(event.data);
|
|
49
|
+
if (frame.type === 'ready') {
|
|
50
|
+
if (this.requested || this.readyState !== 0)
|
|
51
|
+
throw new Error('Repeated relay handshake');
|
|
52
|
+
entryReadyAt = performance.now();
|
|
53
|
+
this.requested = true;
|
|
54
|
+
this.transmit({ type: 'open', streamId: this.streamId, serviceId: targetServiceId });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (frame.streamId !== this.streamId)
|
|
58
|
+
return;
|
|
59
|
+
if (frame.type === 'opened' && this.readyState === 0) {
|
|
60
|
+
this.connectionTimings.routeOpenMs = entryReadyAt === undefined ? null : performance.now() - entryReadyAt;
|
|
61
|
+
clearTimeout(this.deadline);
|
|
62
|
+
this.readyState = 1;
|
|
63
|
+
const e = new Event('open');
|
|
64
|
+
this.dispatchEvent(e);
|
|
65
|
+
this.onopen?.(e);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (frame.type === 'close') {
|
|
69
|
+
if (frame.code === 'RELAY_AUTHORIZATION_REQUIRED' || frame.reason === 'Authorization revoked') {
|
|
70
|
+
this.transportFailure = new ConnectionAccessError('RELAY_AUTHORIZATION_REQUIRED', { ...context, targetServiceId });
|
|
71
|
+
}
|
|
72
|
+
if (frame.code === 'RELAY_SELF_TARGET')
|
|
73
|
+
this.transportFailure = new ConnectionAccessError('RELAY_SELF_TARGET', { ...context, targetServiceId });
|
|
74
|
+
this.finish(1001, this.transportFailure?.message || 'Relay route closed');
|
|
75
|
+
this.upstream.close();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (frame.type !== 'data' || this.readyState !== 1 || frame.encoding !== 'base64' || typeof frame.payload !== 'string'
|
|
79
|
+
|| !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(frame.payload))
|
|
80
|
+
throw new Error('Invalid ciphertext relay frame');
|
|
81
|
+
const bytes = fromBase64(frame.payload);
|
|
82
|
+
const data = this.binaryType === 'blob' ? new Blob([bytes]) : bytes.buffer;
|
|
83
|
+
const e = new MessageEvent('message', { data });
|
|
84
|
+
this.dispatchEvent(e);
|
|
85
|
+
this.onmessage?.(e);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
this.fail('Relay protocol rejected frame');
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
this.upstream.addEventListener('close', event => this.finish(event.code || 1006, 'Relay disconnected'));
|
|
92
|
+
this.upstream.addEventListener('error', () => this.fail('Relay transport unavailable'));
|
|
93
|
+
}
|
|
94
|
+
transmit(frame) {
|
|
95
|
+
const data = JSON.stringify(frame);
|
|
96
|
+
if (this.upstream.readyState !== 1 || this.upstream.bufferedAmount + data.length > MAX_BUFFER_BYTES || data.length > MAX_FRAME_BYTES)
|
|
97
|
+
throw new Error('Relay unavailable or congested');
|
|
98
|
+
this.upstream.send(data);
|
|
99
|
+
}
|
|
100
|
+
send(data) {
|
|
101
|
+
if (this.readyState !== 1)
|
|
102
|
+
throw new Error('Relay socket is not open');
|
|
103
|
+
let bytes;
|
|
104
|
+
if (ArrayBuffer.isView(data))
|
|
105
|
+
bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
106
|
+
else if (data instanceof ArrayBuffer)
|
|
107
|
+
bytes = new Uint8Array(data);
|
|
108
|
+
else
|
|
109
|
+
throw new Error('Relay secure transport requires binary bytes');
|
|
110
|
+
try {
|
|
111
|
+
this.transmit({ type: 'data', streamId: this.streamId, encoding: 'base64', payload: toBase64(bytes) });
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
this.fail('Relay transport congested');
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
close(code = 1000, reason = '') {
|
|
119
|
+
if (this.readyState === 3)
|
|
120
|
+
return;
|
|
121
|
+
try {
|
|
122
|
+
if (this.requested)
|
|
123
|
+
this.transmit({ type: 'close', streamId: this.streamId });
|
|
124
|
+
}
|
|
125
|
+
catch { /* connection already unavailable */ }
|
|
126
|
+
this.finish(code, reason);
|
|
127
|
+
this.upstream.close();
|
|
128
|
+
}
|
|
129
|
+
fail(reason) {
|
|
130
|
+
if (this.readyState === 3)
|
|
131
|
+
return;
|
|
132
|
+
const e = new Event('error');
|
|
133
|
+
this.dispatchEvent(e);
|
|
134
|
+
this.onerror?.(e);
|
|
135
|
+
this.finish(1006, reason);
|
|
136
|
+
this.upstream.close();
|
|
137
|
+
}
|
|
138
|
+
finish(code, reason) {
|
|
139
|
+
if (this.readyState === 3)
|
|
140
|
+
return;
|
|
141
|
+
clearTimeout(this.deadline);
|
|
142
|
+
this.readyState = 3;
|
|
143
|
+
const e = Object.assign(new Event('close'), { code, reason, wasClean: code === 1000 });
|
|
144
|
+
this.dispatchEvent(e);
|
|
145
|
+
this.onclose?.(e);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -157,9 +157,11 @@ export class RouteAccess {
|
|
|
157
157
|
renameSync(temporary, this.filePath);
|
|
158
158
|
}
|
|
159
159
|
configuredDirectTargets() {
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
const explicit = this.registry().directTargets;
|
|
161
|
+
const overridden = new Set(explicit.map(target => target.serviceId));
|
|
162
|
+
const targets = new Map(this.additionalTargets().filter(target => !overridden.has(target.serviceId)).map(target => [JSON.stringify([target.serviceId, target.url]), target]));
|
|
163
|
+
for (const target of explicit)
|
|
164
|
+
targets.set(JSON.stringify([target.serviceId, target.url]), target);
|
|
163
165
|
return [...targets.values()];
|
|
164
166
|
}
|
|
165
167
|
configuredTargets() {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { androidRouteAccess } from '../shared/androidDevices.js';
|
|
1
2
|
import { SessionSharingPresence } from './sessionSharing.js';
|
|
2
3
|
import { collaborationSessionDisplayName } from '../utils/sessionDisplayName.js';
|
|
3
4
|
import { mergeShareResources } from '../shared/shareResources.js';
|
|
@@ -15,6 +16,7 @@ import { createIdentity, importIdentity, exportIdentity, secureConnection } from
|
|
|
15
16
|
import { InvitationStore, validateInvitation } from './invitations.js';
|
|
16
17
|
import { AuthorizationStore } from './authorization.js';
|
|
17
18
|
import { resolveAccessRequest, isBusinessApiPath, terminalMessageAction } from './accessPolicy.js';
|
|
19
|
+
import { httpUploadLimit } from './httpUploadLimit.js';
|
|
18
20
|
import { AsyncQueue, PacketChannel, toBase64, fromBase64 } from './packets.js';
|
|
19
21
|
import { socketDuplex } from './socketDuplex.js';
|
|
20
22
|
import { markEncryptedRequest } from './requestContext.js';
|
|
@@ -171,7 +173,7 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
171
173
|
return stable !== undefined && store.authorize({ subjectId, serviceId, action, sessionId: stable }).allowed;
|
|
172
174
|
};
|
|
173
175
|
const resources = (subjectId) => mergeShareResources(store.listEffective({ subjectId, serviceId }).map(g => g.resources));
|
|
174
|
-
const androidAllowed = (subjectId, serial, control = false) => full(subjectId) || resources(subjectId).android
|
|
176
|
+
const androidAllowed = (subjectId, serial, control = false) => full(subjectId) || (control ? androidRouteAccess(resources(subjectId).android, serial) === 'control' : !!androidRouteAccess(resources(subjectId).android, serial));
|
|
175
177
|
const computerAllowed = (subjectId, target) => full(subjectId) || !!computerResource(resources(subjectId), target.host, target.protocol, target.port);
|
|
176
178
|
const full = (subjectId) => open() || store.hasFullServiceAccess({ subjectId, serviceId });
|
|
177
179
|
let channel;
|
|
@@ -585,7 +587,7 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
585
587
|
httpAllowed(operation.head);
|
|
586
588
|
const bytes = operation.head.binaryChunks === true && packet.bytes instanceof Uint8Array ? packet.bytes : fromBase64(packet.data);
|
|
587
589
|
operation.size += bytes.length;
|
|
588
|
-
const limit = new URL(String(operation.head.path), 'http://inner').pathname
|
|
590
|
+
const limit = httpUploadLimit(new URL(String(operation.head.path), 'http://inner').pathname);
|
|
589
591
|
if (['GET', 'HEAD'].includes(String(operation.head.method)) || bytes.length > TRANSFER_CHUNK_BYTES || operation.size > limit) {
|
|
590
592
|
cancelHttp(operation);
|
|
591
593
|
operations.delete(packet.id);
|
|
@@ -3,60 +3,14 @@ import { readFileSync } from 'node:fs';
|
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import { desktopDirectTargets } from './desktopTargets.js';
|
|
5
5
|
import { RelayClient } from './relay.js';
|
|
6
|
-
import {
|
|
6
|
+
import { openServiceSocket } from './serviceTransport.js';
|
|
7
|
+
import { serviceEndpoint, normalizeServiceAddress } from './connectionModel.js';
|
|
7
8
|
import { connectCollaborationRpc } from '../agent/collaborationPeerTransport.js';
|
|
8
9
|
function relayEndpoint(origin, selfRelayToken) {
|
|
9
|
-
const url =
|
|
10
|
-
url.protocol = 'wss:';
|
|
10
|
+
const url = serviceEndpoint(origin, 'relay');
|
|
11
11
|
url.searchParams.set('selfRelayToken', selfRelayToken);
|
|
12
12
|
return url.href;
|
|
13
13
|
}
|
|
14
|
-
function secureEndpoint(origin) {
|
|
15
|
-
const url = new URL('/api/federation/secure', origin);
|
|
16
|
-
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
17
|
-
return url.href;
|
|
18
|
-
}
|
|
19
|
-
function openSocket(url, ca, maxPayload = 256 * 1024) {
|
|
20
|
-
return new Promise((resolve, reject) => {
|
|
21
|
-
const secure = new URL(url).protocol === 'wss:';
|
|
22
|
-
const socket = new WebSocket(url, {
|
|
23
|
-
...(secure ? { ...(ca ? { ca } : {}), rejectUnauthorized: true } : {}),
|
|
24
|
-
followRedirects: false,
|
|
25
|
-
handshakeTimeout: 10_000,
|
|
26
|
-
maxPayload,
|
|
27
|
-
});
|
|
28
|
-
const failed = () => { cleanup(); socket.terminate(); reject(new Error('SELF_RELAY_CONNECTION_FAILED')); };
|
|
29
|
-
const opened = () => { cleanup(); socket.on('error', () => socket.terminate()); resolve(socket); };
|
|
30
|
-
const cleanup = () => { socket.off('open', opened); socket.off('error', failed); socket.off('close', failed); };
|
|
31
|
-
socket.once('open', opened);
|
|
32
|
-
socket.once('error', failed);
|
|
33
|
-
socket.once('close', failed);
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
function waitForReady(socket) {
|
|
37
|
-
return new Promise((resolve, reject) => {
|
|
38
|
-
const timer = setTimeout(() => fail(), 10_000);
|
|
39
|
-
timer.unref?.();
|
|
40
|
-
const cleanup = () => { clearTimeout(timer); socket.off('message', ready); socket.off('close', fail); };
|
|
41
|
-
const fail = () => { cleanup(); socket.terminate(); reject(new Error('SELF_RELAY_AUTHENTICATION_FAILED')); };
|
|
42
|
-
const ready = (raw, binary) => {
|
|
43
|
-
try {
|
|
44
|
-
const frame = binary ? null : JSON.parse(raw.toString());
|
|
45
|
-
if (frame?.type !== 'ready') {
|
|
46
|
-
fail();
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
cleanup();
|
|
50
|
-
resolve();
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
fail();
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
socket.once('message', ready);
|
|
57
|
-
socket.once('close', fail);
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
14
|
/**
|
|
61
15
|
* A local Termdock service may publish only its own stable identity to other
|
|
62
16
|
* administrator-configured peers or services pinned by the same Desktop
|
|
@@ -93,10 +47,10 @@ export class DesktopSelfRelay {
|
|
|
93
47
|
return;
|
|
94
48
|
const desired = new Map([...desktopDirectTargets(this.options.desktopFile, this.options.serviceId), ...(this.options.targets?.() || [])]
|
|
95
49
|
.filter(target => target.serviceId !== this.options.serviceId)
|
|
96
|
-
.map(target => [target.serviceId, { ...target, url:
|
|
50
|
+
.map(target => [JSON.stringify([target.serviceId, normalizeServiceAddress(target.url)]), { ...target, url: normalizeServiceAddress(target.url) }]));
|
|
97
51
|
for (const [serviceId, state] of this.states) {
|
|
98
52
|
const target = desired.get(serviceId);
|
|
99
|
-
const signature = target && JSON.stringify([target.url, target.caFingerprint256, target.caPath]);
|
|
53
|
+
const signature = target && JSON.stringify([target.url, target.caFingerprint256, target.caPath, target.certificateFingerprint256]);
|
|
100
54
|
if (!target || signature !== state.signature) {
|
|
101
55
|
this.stopState(state);
|
|
102
56
|
this.states.delete(serviceId);
|
|
@@ -104,7 +58,7 @@ export class DesktopSelfRelay {
|
|
|
104
58
|
}
|
|
105
59
|
for (const [serviceId, target] of desired)
|
|
106
60
|
if (!this.states.has(serviceId)) {
|
|
107
|
-
const state = { signature: JSON.stringify([target.url, target.caFingerprint256, target.caPath]), stopped: false };
|
|
61
|
+
const state = { signature: JSON.stringify([target.url, target.caFingerprint256, target.caPath, target.certificateFingerprint256]), stopped: false, lifetime: new AbortController() };
|
|
108
62
|
this.states.set(serviceId, state);
|
|
109
63
|
void this.run(state, target);
|
|
110
64
|
}
|
|
@@ -113,6 +67,7 @@ export class DesktopSelfRelay {
|
|
|
113
67
|
if (state.stopped)
|
|
114
68
|
return;
|
|
115
69
|
state.stopped = true;
|
|
70
|
+
state.lifetime.abort();
|
|
116
71
|
state.wake?.();
|
|
117
72
|
state.wake = undefined;
|
|
118
73
|
state.client?.close();
|
|
@@ -141,7 +96,7 @@ export class DesktopSelfRelay {
|
|
|
141
96
|
async connect(state, target) {
|
|
142
97
|
const peer = { serviceId: target.serviceId, origin: target.url, ...(target.caFingerprint256 ? { caFingerprint256: target.caFingerprint256 } : {}) };
|
|
143
98
|
const configuredCa = target.caPath ? readFileSync(resolve(this.options.baseDir || process.cwd(), target.caPath)) : undefined;
|
|
144
|
-
const rpc = await connectCollaborationRpc(this.options.identity, peer, undefined, undefined, configuredCa);
|
|
99
|
+
const rpc = await connectCollaborationRpc(this.options.identity, peer, undefined, undefined, configuredCa, target.certificateFingerprint256, state.lifetime.signal);
|
|
145
100
|
let selfRelayToken;
|
|
146
101
|
try {
|
|
147
102
|
const result = await rpc.request({ type: 'self-relay-ticket' });
|
|
@@ -155,17 +110,13 @@ export class DesktopSelfRelay {
|
|
|
155
110
|
}
|
|
156
111
|
if (this.stopped || state.stopped)
|
|
157
112
|
return;
|
|
158
|
-
const
|
|
159
|
-
const upstream = await openSocket(relayEndpoint(target.url, selfRelayToken), ca);
|
|
113
|
+
const upstream = await openServiceSocket(relayEndpoint(target.url, selfRelayToken), target, { baseDir: this.options.baseDir, maxPayload: 256 * 1024, relayReady: true, signal: state.lifetime.signal });
|
|
160
114
|
state.upstream = upstream;
|
|
161
115
|
try {
|
|
162
|
-
if (this.stopped || state.stopped)
|
|
163
|
-
return;
|
|
164
|
-
await waitForReady(upstream);
|
|
165
116
|
if (this.stopped || state.stopped)
|
|
166
117
|
return;
|
|
167
118
|
const client = new RelayClient(upstream, { targets: new Map([[this.options.serviceId,
|
|
168
|
-
() =>
|
|
119
|
+
(signal) => openServiceSocket(serviceEndpoint(typeof this.options.localOrigin === 'function' ? this.options.localOrigin() : this.options.localOrigin).href, { ca: this.options.localCa }, { signal: AbortSignal.any([signal, state.lifetime.signal]) })]]) });
|
|
169
120
|
state.client = client;
|
|
170
121
|
await new Promise(resolve => {
|
|
171
122
|
if (upstream.readyState !== WebSocket.OPEN)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import * as tls from 'node:tls';
|
|
4
|
+
import WebSocket from 'ws';
|
|
5
|
+
import { normalizeServiceAddress } from './connectionModel.js';
|
|
6
|
+
import { pinnedServerTlsOptions, readPinnedCertificateAuthority } from './trustedCa.js';
|
|
7
|
+
import { RELAY_MAX_FRAME_BYTES } from './streamLimits.js';
|
|
8
|
+
/** One trust decision for probes, direct providers, reverse relays and peer RPC.
|
|
9
|
+
* A pinned leaf, a pinned CA and a CA file are distinct trust modes. */
|
|
10
|
+
export async function serviceTlsOptions(address, trust = {}, baseDir = process.cwd()) {
|
|
11
|
+
const origin = normalizeServiceAddress(address);
|
|
12
|
+
const modes = [trust.ca, trust.caPath, trust.caFingerprint256, trust.certificateFingerprint256].filter(value => value !== undefined);
|
|
13
|
+
if (modes.length > 1)
|
|
14
|
+
throw new Error('Conflicting service certificate trust');
|
|
15
|
+
if (trust.certificateFingerprint256 !== undefined)
|
|
16
|
+
return pinnedServerTlsOptions(origin, trust.certificateFingerprint256);
|
|
17
|
+
if (trust.caFingerprint256 !== undefined)
|
|
18
|
+
return { ca: await readPinnedCertificateAuthority(origin, trust.caFingerprint256) };
|
|
19
|
+
if (trust.caPath !== undefined && !trust.caPath)
|
|
20
|
+
throw new Error('Invalid service CA path');
|
|
21
|
+
if (!modes.length) {
|
|
22
|
+
// Match native clients' OS-approved trust without changing process-global
|
|
23
|
+
// TLS defaults. Explicit pins/CA files never inherit these additional roots.
|
|
24
|
+
const getCertificates = tls.getCACertificates;
|
|
25
|
+
if (getCertificates)
|
|
26
|
+
return { ca: [...new Set([...getCertificates('default'), ...getCertificates('system')])] };
|
|
27
|
+
}
|
|
28
|
+
return { ca: trust.caPath ? readFileSync(resolve(baseDir, trust.caPath)) : trust.ca };
|
|
29
|
+
}
|
|
30
|
+
/** Return a connecting socket, so relay listeners are installed before the
|
|
31
|
+
* entry's ready frame. No business bytes are sent during TLS pin inspection. */
|
|
32
|
+
export async function createServiceSocket(address, trust = {}, options = {}) {
|
|
33
|
+
const url = new URL(address);
|
|
34
|
+
const origin = normalizeServiceAddress(url.origin.replace(/^wss:/, 'https:').replace(/^ws:/, 'http:'));
|
|
35
|
+
if (!['wss:', 'ws:'].includes(url.protocol) || url.username || url.password || url.hash
|
|
36
|
+
|| !['/api/federation/secure', '/api/federation/relay'].includes(url.pathname)
|
|
37
|
+
|| [...url.searchParams.keys()].length > 1
|
|
38
|
+
|| [...url.searchParams.keys()].some(key => url.pathname !== '/api/federation/relay' || !['routeToken', 'selfRelayToken'].includes(key))
|
|
39
|
+
|| url.search && Object.keys(options.headers || {}).some(key => key.toLowerCase() === 'authorization'))
|
|
40
|
+
throw new Error('Invalid service transport endpoint');
|
|
41
|
+
const tls = await serviceTlsOptions(origin, trust, options.baseDir);
|
|
42
|
+
options.signal?.throwIfAborted();
|
|
43
|
+
const socket = new WebSocket(url, { ...tls, rejectUnauthorized: true, followRedirects: false, perMessageDeflate: false,
|
|
44
|
+
maxPayload: options.maxPayload ?? 1024 * 1024, handshakeTimeout: options.handshakeTimeout ?? 5000,
|
|
45
|
+
...(options.headers ? { headers: options.headers } : {}) });
|
|
46
|
+
const abort = () => socket.terminate();
|
|
47
|
+
options.signal?.addEventListener('abort', abort, { once: true });
|
|
48
|
+
socket.once('close', () => options.signal?.removeEventListener('abort', abort));
|
|
49
|
+
socket.on('error', () => socket.terminate());
|
|
50
|
+
return socket;
|
|
51
|
+
}
|
|
52
|
+
export function waitForServiceSocket(socket, signal) {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const cleanup = () => {
|
|
55
|
+
socket.removeEventListener('open', opened);
|
|
56
|
+
socket.removeEventListener('error', failed);
|
|
57
|
+
socket.removeEventListener('close', failed);
|
|
58
|
+
signal?.removeEventListener('abort', aborted);
|
|
59
|
+
};
|
|
60
|
+
const opened = () => { cleanup(); resolve(); };
|
|
61
|
+
const failed = () => { cleanup(); reject(socket.transportFailure || new Error('Service transport unavailable')); };
|
|
62
|
+
const aborted = () => { cleanup(); reject(signal?.reason); };
|
|
63
|
+
if (signal?.aborted) {
|
|
64
|
+
aborted();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (socket.readyState === 1) {
|
|
68
|
+
opened();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (socket.readyState >= 2) {
|
|
72
|
+
failed();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
socket.addEventListener('open', opened);
|
|
76
|
+
socket.addEventListener('error', failed);
|
|
77
|
+
socket.addEventListener('close', failed);
|
|
78
|
+
signal?.addEventListener('abort', aborted, { once: true });
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export async function openServiceSocket(address, trust = {}, options = {}) {
|
|
82
|
+
const socket = await createServiceSocket(address, trust, options);
|
|
83
|
+
// Subscribe before open: an upgrade response and ready frame can share a packet.
|
|
84
|
+
const ready = options.relayReady ? relayEntryReady(socket, options.signal) : undefined;
|
|
85
|
+
try {
|
|
86
|
+
await waitForServiceSocket(socket, options.signal);
|
|
87
|
+
if (ready)
|
|
88
|
+
await ready;
|
|
89
|
+
if (socket.readyState !== WebSocket.OPEN)
|
|
90
|
+
throw new Error('Service transport closed during handshake');
|
|
91
|
+
return socket;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
socket.terminate();
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function relayEntryReady(socket, signal) {
|
|
99
|
+
const ready = new Promise((resolve, reject) => {
|
|
100
|
+
const cleanup = () => { clearTimeout(timer); socket.off('message', message); socket.off('close', failed); signal?.removeEventListener('abort', aborted); };
|
|
101
|
+
const failed = () => { cleanup(); reject(new Error('Relay entry handshake failed')); };
|
|
102
|
+
const aborted = () => { cleanup(); reject(signal?.reason); };
|
|
103
|
+
const message = (raw, binary) => {
|
|
104
|
+
try {
|
|
105
|
+
if (binary || Buffer.byteLength(Array.isArray(raw) ? Buffer.concat(raw) : raw) > RELAY_MAX_FRAME_BYTES || JSON.parse(raw.toString())?.type !== 'ready')
|
|
106
|
+
throw new Error('Invalid entry handshake');
|
|
107
|
+
cleanup();
|
|
108
|
+
resolve();
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
failed();
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const timer = setTimeout(failed, 10_000);
|
|
115
|
+
socket.once('message', message);
|
|
116
|
+
socket.once('close', failed);
|
|
117
|
+
signal?.addEventListener('abort', aborted, { once: true });
|
|
118
|
+
if (signal?.aborted)
|
|
119
|
+
aborted();
|
|
120
|
+
});
|
|
121
|
+
void ready.catch(() => { });
|
|
122
|
+
return ready;
|
|
123
|
+
}
|
|
@@ -8,3 +8,6 @@ export const MAX_OPEN_SECURE_SOCKETS = 128;
|
|
|
8
8
|
export const RELAY_PROVIDER_CONNECT_TIMEOUT_MS = 10_000;
|
|
9
9
|
export const RELAY_ROUTE_OPEN_TIMEOUT_MS = 15_000;
|
|
10
10
|
export const RELAY_CLIENT_CONNECT_TIMEOUT_MS = 20_000;
|
|
11
|
+
export const RELAY_MAX_FRAME_BYTES = 256 * 1024;
|
|
12
|
+
export const RELAY_MAX_BUFFERED_BYTES = 1024 * 1024;
|
|
13
|
+
export const CONNECTION_ROUND_TIMEOUT_MS = 30_000;
|
|
@@ -1,6 +1,67 @@
|
|
|
1
1
|
import { X509Certificate } from 'node:crypto';
|
|
2
2
|
import { get } from 'node:https';
|
|
3
|
+
import { connect, checkServerIdentity } from 'node:tls';
|
|
3
4
|
const cache = new Map();
|
|
5
|
+
const leafCache = new Map();
|
|
6
|
+
/** Inspect TLS only, without sending HTTP or business data. Preview approves
|
|
7
|
+
* the leaf itself, so a downloaded CA cannot substitute for that approval. */
|
|
8
|
+
export async function pinnedServerTlsOptions(origin, fingerprint) {
|
|
9
|
+
const url = new URL(origin);
|
|
10
|
+
if (url.protocol !== 'https:' || url.username || url.password || !/^[\da-f]{64}$/i.test(fingerprint))
|
|
11
|
+
throw new Error('Invalid pinned server certificate');
|
|
12
|
+
const pin = fingerprint.toUpperCase();
|
|
13
|
+
const key = `${url.origin}:${pin}`;
|
|
14
|
+
let cached = leafCache.get(key);
|
|
15
|
+
if (!cached || cached.expires <= Date.now()) {
|
|
16
|
+
const value = new Promise((resolve, reject) => {
|
|
17
|
+
const socket = connect({ host: url.hostname.replace(/^\[|\]$/g, ''), port: Number(url.port || 443),
|
|
18
|
+
...(url.hostname.includes(':') || /^[\d.]+$/.test(url.hostname) ? {} : { servername: url.hostname }),
|
|
19
|
+
rejectUnauthorized: false });
|
|
20
|
+
const timer = setTimeout(() => socket.destroy(new Error('Pinned server certificate inspection timed out')), 5000);
|
|
21
|
+
socket.once('error', reject);
|
|
22
|
+
socket.once('close', () => { clearTimeout(timer); reject(new Error('Pinned server certificate inspection closed')); });
|
|
23
|
+
socket.once('secureConnect', () => {
|
|
24
|
+
try {
|
|
25
|
+
const raw = socket.getPeerCertificate().raw;
|
|
26
|
+
if (!raw)
|
|
27
|
+
throw new Error('Server did not present a certificate');
|
|
28
|
+
const cert = new X509Certificate(raw);
|
|
29
|
+
const hostError = checkServerIdentity(url.hostname.replace(/^\[|\]$/g, ''), socket.getPeerCertificate());
|
|
30
|
+
if (hostError)
|
|
31
|
+
throw hostError;
|
|
32
|
+
if (cert.fingerprint256.replaceAll(':', '') !== pin || Date.now() < Date.parse(cert.validFrom)
|
|
33
|
+
|| Date.now() > Date.parse(cert.validTo))
|
|
34
|
+
throw new Error('Server certificate no longer matches Preview trust');
|
|
35
|
+
resolve(Buffer.from(cert.toString()));
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
reject(error);
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
socket.destroy();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
cached = { expires: Date.now() + 120_000, value };
|
|
46
|
+
if (leafCache.size >= 64)
|
|
47
|
+
leafCache.delete(leafCache.keys().next().value);
|
|
48
|
+
leafCache.set(key, cached);
|
|
49
|
+
void value.catch(() => { if (leafCache.get(key)?.value === value)
|
|
50
|
+
leafCache.delete(key); });
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
ca: await cached.value,
|
|
54
|
+
// A CA-issued leaf can be the exact trust anchor without importing its CA.
|
|
55
|
+
allowPartialTrustChain: true,
|
|
56
|
+
checkServerIdentity(host, cert) {
|
|
57
|
+
const error = checkServerIdentity(host, cert);
|
|
58
|
+
if (error)
|
|
59
|
+
return error;
|
|
60
|
+
if (cert.fingerprint256?.replaceAll(':', '').toUpperCase() !== pin)
|
|
61
|
+
return new Error('Server certificate no longer matches Preview trust');
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
4
65
|
/** Only the CA download is unauthenticated. Its exact SHA-256 fingerprint was
|
|
5
66
|
* already approved locally for this origin; the subsequent WSS connection must
|
|
6
67
|
* still pass normal TLS chain, hostname and expiry checks using this CA.
|
|
@@ -6,12 +6,10 @@ import { createScrcpySession, resolveScrcpyBinaries } from '../android/scrcpy.js
|
|
|
6
6
|
import { androidRecordings } from '../android/recording.js';
|
|
7
7
|
import { pathValidator } from '../utils/pathValidator.js';
|
|
8
8
|
const router = Router();
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
import { isValidAndroidSerial, androidRouteAccess } from '../shared/androidDevices.js';
|
|
10
|
+
export { isValidAndroidSerial };
|
|
11
|
+
/** Opt-in per-frame diagnostics. */
|
|
11
12
|
const DEBUG_ANDROID = process.env.TERMDOCK_ANDROID_DEBUG === '1';
|
|
12
|
-
export function isValidAndroidSerial(serial) {
|
|
13
|
-
return serial.length <= 128 && SERIAL_PATTERN.test(serial);
|
|
14
|
-
}
|
|
15
13
|
router.get('/devices', async (req, res) => {
|
|
16
14
|
try {
|
|
17
15
|
const devices = await listDevices();
|
|
@@ -24,7 +22,7 @@ router.get('/devices', async (req, res) => {
|
|
|
24
22
|
scrcpyVersion = null;
|
|
25
23
|
}
|
|
26
24
|
const shared = encryptedRequestResources(req);
|
|
27
|
-
res.json({ adbAvailable, scrcpyVersion, devices: shared && !hasEncryptedServiceAccess(req) ? devices.filter(d => shared.android
|
|
25
|
+
res.json({ adbAvailable, scrcpyVersion, devices: shared && !hasEncryptedServiceAccess(req) ? devices.filter(d => androidRouteAccess(shared.android, d.serial)) : devices });
|
|
28
26
|
}
|
|
29
27
|
catch (error) {
|
|
30
28
|
res.status(503).json({ error: error instanceof Error ? error.message : 'ADB_UNAVAILABLE', code: 'ADB_UNAVAILABLE' });
|
|
@@ -66,7 +64,7 @@ router.use('/recordings', async (req, res, next) => {
|
|
|
66
64
|
try {
|
|
67
65
|
const id = /^\/([^/]+)(?:\/(?:stop|save))?$/.exec(req.path)?.[1];
|
|
68
66
|
const serial = id ? (await androidRecordings.info(id, subject)).serial : req.method === 'GET' ? req.query.serial : req.body?.serial;
|
|
69
|
-
if (typeof serial !== 'string' || !resources.android
|
|
67
|
+
if (typeof serial !== 'string' || !androidRouteAccess(resources.android, serial))
|
|
70
68
|
throw new Error('PHONE_NOT_SHARED');
|
|
71
69
|
if (req.body?.directory !== undefined && (typeof req.body.directory !== 'string' || !directoryResource(resources, req.body.directory, true)))
|
|
72
70
|
throw new Error('DIRECTORY_NOT_SHARED');
|