termdock 1.4.351 → 1.4.353
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/dist/client/assets/AndroidMirrorView-C39BgFa_.js +3 -0
- package/dist/client/assets/AndroidMirrorView-C39BgFa_.js.br +0 -0
- package/dist/client/assets/AndroidMirrorView-C39BgFa_.js.gz +0 -0
- package/dist/client/assets/App-AjjtXg93.js +178 -0
- package/dist/client/assets/App-AjjtXg93.js.br +0 -0
- package/dist/client/assets/App-AjjtXg93.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-Xi_R4Tso.js +9 -0
- package/dist/client/assets/ArchitecturePanel-Xi_R4Tso.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-Xi_R4Tso.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BH1Una8m.js +2 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BH1Una8m.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BH1Una8m.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-CA9ZGT5N.js +1 -0
- package/dist/client/assets/CollaborationMainWorkspace-CA9ZGT5N.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-CA9ZGT5N.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-D_NDzA-t.js +2 -0
- package/dist/client/assets/ComputerControlView-BGvF5NeQ.js +6 -0
- package/dist/client/assets/ComputerControlView-BGvF5NeQ.js.br +0 -0
- package/dist/client/assets/ComputerControlView-BGvF5NeQ.js.gz +0 -0
- package/dist/client/assets/DagPlayground-CKqB6zap.js +1 -0
- package/dist/client/assets/DagPlayground-CKqB6zap.js.br +0 -0
- package/dist/client/assets/DagPlayground-CKqB6zap.js.gz +0 -0
- package/dist/client/assets/{DiffLab-DFut8ZHi.js → DiffLab-CDt8EP6I.js} +2 -2
- package/dist/client/assets/DiffLab-CDt8EP6I.js.br +0 -0
- package/dist/client/assets/DiffLab-CDt8EP6I.js.gz +0 -0
- package/dist/client/assets/DiffReview-YADkwVd6.js +1 -0
- package/dist/client/assets/DiffReview-YADkwVd6.js.br +0 -0
- package/dist/client/assets/DiffReview-YADkwVd6.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-Y9JlI5DQ.js → DiffReviewLab-YqgsRn_K.js} +2 -2
- package/dist/client/assets/DiffReviewLab-YqgsRn_K.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-YqgsRn_K.js.gz +0 -0
- package/dist/client/assets/DiffViewer-CT5_YwM2.js +34 -0
- package/dist/client/assets/DiffViewer-CT5_YwM2.js.br +0 -0
- package/dist/client/assets/DiffViewer-CT5_YwM2.js.gz +0 -0
- package/dist/client/assets/GitLoadingSkeleton-DfWzAnA2.js +1 -0
- package/dist/client/assets/GitLoadingSkeleton-DfWzAnA2.js.br +0 -0
- package/dist/client/assets/GitLoadingSkeleton-DfWzAnA2.js.gz +0 -0
- package/dist/client/assets/MarkdownPreview-DHLp5cqt.js +19 -0
- package/dist/client/assets/MarkdownPreview-DHLp5cqt.js.br +0 -0
- package/dist/client/assets/MarkdownPreview-DHLp5cqt.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-Cv6JgunE.js → ModelPreview-DymFzc55.js} +166 -166
- package/dist/client/assets/ModelPreview-DymFzc55.js.br +0 -0
- package/dist/client/assets/ModelPreview-DymFzc55.js.gz +0 -0
- package/dist/client/assets/RecordingSaveDialog-CxSN76bp.js +1 -0
- package/dist/client/assets/RecordingSaveDialog-CxSN76bp.js.br +0 -0
- package/dist/client/assets/RecordingSaveDialog-CxSN76bp.js.gz +0 -0
- package/dist/client/assets/RightSidebar-eIuDnhJn.js +1662 -0
- package/dist/client/assets/RightSidebar-eIuDnhJn.js.br +0 -0
- package/dist/client/assets/RightSidebar-eIuDnhJn.js.gz +0 -0
- package/dist/client/assets/SessionChatView-C5wXAt7h.js +13 -0
- package/dist/client/assets/SessionChatView-C5wXAt7h.js.br +0 -0
- package/dist/client/assets/SessionChatView-C5wXAt7h.js.gz +0 -0
- package/dist/client/assets/SessionChatView-D8GoESjb.css +1 -0
- 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-Czai5Bt3.js +2 -0
- package/dist/client/assets/SessionResourceSidebar-Czai5Bt3.js.br +0 -0
- package/dist/client/assets/SessionResourceSidebar-Czai5Bt3.js.gz +0 -0
- package/dist/client/assets/TodoWorkspace-BftnnEU0.js +1 -0
- package/dist/client/assets/TodoWorkspace-BftnnEU0.js.br +0 -0
- package/dist/client/assets/TodoWorkspace-BftnnEU0.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-CcCffdpj.js → abnfDiagram-VCTEODGH-DI1Y-QAP.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-DI1Y-QAP.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-DI1Y-QAP.js.gz +0 -0
- package/dist/client/assets/app-DvMXSNN7.js +36 -0
- package/dist/client/assets/app-DvMXSNN7.js.br +0 -0
- package/dist/client/assets/app-DvMXSNN7.js.gz +0 -0
- package/dist/client/assets/{arc-CXcCuzb3.js → arc-QdJAJx_D.js} +1 -1
- package/dist/client/assets/arc-QdJAJx_D.js.br +0 -0
- package/dist/client/assets/arc-QdJAJx_D.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-CmR7whZz.js → architectureDiagram-5GKGNRK7-Bw2CvoGg.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-Bw2CvoGg.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-Bw2CvoGg.js.gz +0 -0
- package/dist/client/assets/arrow-down-CZKuaGwN.js +1 -0
- package/dist/client/assets/arrow-up-CYLC6_to.js +1 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-DODhUpHm.js → blockDiagram-I7D4REHJ-CM2GI6sC.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-CM2GI6sC.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-CM2GI6sC.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-BF9eEb2Y.js → c4Diagram-7LVT6UL2-BAB2TKr4.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-BAB2TKr4.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-BAB2TKr4.js.gz +0 -0
- package/dist/client/assets/channel-CcDRt0AM.js +1 -0
- package/dist/client/assets/chevron-left-BNby2AjR.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-CB7-8Zex.js → chunk-2Q5K7J3B-DI2pKKMQ.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-8Uxe2lki.js → chunk-5VM5RSS4-D5l5zyYl.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-Bv45U-az.js → chunk-F27PBJKO-Japl432v.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-Bv45U-az.js.br → chunk-F27PBJKO-Japl432v.js.br} +0 -0
- package/dist/client/assets/chunk-F27PBJKO-Japl432v.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-CfEErbEd.js → chunk-IMKFNOWR-CDJoLgpw.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-CDJoLgpw.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CDJoLgpw.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-CjUqcCSL.js → chunk-JWPE2WC7-BQ2cpPDE.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-BHbP3O75.js → chunk-POPQ4Y6H-CWjI0yRx.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-CbyJW-OT.js → chunk-SVP7TREG-BzEphMpl.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-BzEphMpl.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-BzEphMpl.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-t93EivmS.js → chunk-TICWLB2K-B2hsw7jN.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-B2hsw7jN.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-B2hsw7jN.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-C_Pe8Osb.js → chunk-XXDRQBXY-BEWjHo7s.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-Dz0qH9Hg.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-Dz0qH9Hg.js +1 -0
- package/dist/client/assets/clipboard-6kE6vzDi.js +1 -0
- package/dist/client/assets/clipboardText-B7202UNa.js +1 -0
- package/dist/client/assets/clipboardText-B7202UNa.js.br +0 -0
- package/dist/client/assets/clipboardText-B7202UNa.js.gz +0 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-CksG0bvP.js → cose-bilkent-JH36ORCC-DpS77pec.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-DpS77pec.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-DpS77pec.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-CV3T078G.js → cynefin-OW5HDTMX-D1y5kNwR.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-D1y5kNwR.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-D1y5kNwR.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-BQnJabEs.js → cynefinDiagram-5FMLGOSQ-CqTCLyMC.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CqTCLyMC.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CqTCLyMC.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-BeIQp5Pv.js → dagre-GXQ25YYZ-DkJ-kRn6.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-DkJ-kRn6.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-DkJ-kRn6.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-D-Mibxiq.js → diagram-S7CK7UJ4-DenrrE_C.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-DenrrE_C.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DenrrE_C.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-eiIMWp9t.js → diagram-UQ7AKVKN-B2ikSD8K.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-B2ikSD8K.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-B2ikSD8K.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-CTVOWtNL.js → diagram-VSXAHHWV-CQcNoZDC.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-CQcNoZDC.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CQcNoZDC.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-NKKk6uh7.js → diagram-VX7I27RA-Cq3FaI-1.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-Cq3FaI-1.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-Cq3FaI-1.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-DKYdBuNs.js → diagram-Z3DM3KII-BcWReLM8.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-BcWReLM8.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-BcWReLM8.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-DaM3D5Ed.js → ebnfDiagram-PWID7BFC-DwwFkjaL.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-DwwFkjaL.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-DwwFkjaL.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-Df9kQwWg.js → erDiagram-RLTQ6QDP-B1MNvouo.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-B1MNvouo.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-B1MNvouo.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-CWjDvpsN.js → flowDiagram-HODETNUW-B_tIl0Vb.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-B_tIl0Vb.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-B_tIl0Vb.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-mKt-CgrH.js → ganttDiagram-EL5Y4UJY-CV0YyZHk.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CV0YyZHk.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CV0YyZHk.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-DszagC-d.js +1 -0
- package/dist/client/assets/gestureArbiter-DszagC-d.js.br +0 -0
- package/dist/client/assets/gestureArbiter-DszagC-d.js.gz +0 -0
- package/dist/client/assets/git-compare-CGiJqTYP.js +1 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-BqnaVVZ0.js → gitGraphDiagram-WWUBYQGX-DZvnSoyE.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DZvnSoyE.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DZvnSoyE.js.gz +0 -0
- package/dist/client/assets/index-CA1fR_jI.css +1 -0
- package/dist/client/assets/index-CA1fR_jI.css.br +0 -0
- package/dist/client/assets/index-CA1fR_jI.css.gz +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-CkniyUX9.js → infoDiagram-27XIBGKW-G1OS50nR.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-CKo9urjS.js → ishikawaDiagram-5VMMS53U-C0-s6pfy.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-C0-s6pfy.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-C0-s6pfy.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-Cd4jK-oI.js → journeyDiagram-3NMN7TZE-B-9Omdit.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-B-9Omdit.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-B-9Omdit.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-BuulZGO-.js → kanban-definition-UXKFOSKX-BS_qXegp.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-BS_qXegp.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BS_qXegp.js.gz +0 -0
- package/dist/client/assets/{layout-BpJ2ofrz.js → layout-0NYin1Qz.js} +1 -1
- package/dist/client/assets/{layout-BpJ2ofrz.js.br → layout-0NYin1Qz.js.br} +0 -0
- package/dist/client/assets/layout-0NYin1Qz.js.gz +0 -0
- package/dist/client/assets/{linear-CaSbGLIA.js → linear-CTVKrsxK.js} +1 -1
- package/dist/client/assets/linear-CTVKrsxK.js.br +0 -0
- package/dist/client/assets/linear-CTVKrsxK.js.gz +0 -0
- package/dist/client/assets/link-C5oPMHMJ.js +8 -0
- package/dist/client/assets/link-C5oPMHMJ.js.br +0 -0
- package/dist/client/assets/link-C5oPMHMJ.js.gz +0 -0
- package/dist/client/assets/list-tree-BPg9QdiM.js +1 -0
- package/dist/client/assets/loader-circle-CTtHKNWd.js +1 -0
- package/dist/client/assets/maximize-2-32OfQ0kG.js +1 -0
- package/dist/client/assets/{mermaid.core-BlkorXd6.js → mermaid.core-BgCZP9ke.js} +5 -5
- package/dist/client/assets/mermaid.core-BgCZP9ke.js.br +0 -0
- package/dist/client/assets/mermaid.core-BgCZP9ke.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-BqKhK1NJ.js → mindmap-definition-YA3MSWOX-BjSZRjkg.js} +2 -2
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BjSZRjkg.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BjSZRjkg.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-CXQgj5nw.js +2 -0
- package/dist/client/assets/passwordBootstrap-CXQgj5nw.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-CXQgj5nw.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-BlWgUklx.js → pegDiagram-XKGWAZYB-qDU0sU6m.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-qDU0sU6m.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-qDU0sU6m.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-Bp32kHHQ.js → pieDiagram-E7YTZNPT-CZF3MEOJ.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-CZF3MEOJ.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-CZF3MEOJ.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-0qeSh9-W.js → quadrantDiagram-AXDQQJYC-DcZW4RMG.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-DcZW4RMG.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-DcZW4RMG.js.gz +0 -0
- package/dist/client/assets/quote-CuTMveNr.js +1 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-COmWghVD.js → railroadDiagram-O6MQD6OU-DtQROK31.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DtQROK31.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DtQROK31.js.gz +0 -0
- package/dist/client/assets/rdpSession-a1ZvAyiW.js +1 -0
- package/dist/client/assets/rdpSession-a1ZvAyiW.js.br +0 -0
- package/dist/client/assets/rdpSession-a1ZvAyiW.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-CeohDJAC.js → requirementDiagram-BXWQKSXE-B8yR39lm.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-B8yR39lm.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-B8yR39lm.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-rA5x9iOw.js → sankeyDiagram-P5KCCOFB-D27zuIFg.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-D27zuIFg.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-D27zuIFg.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-Cw2BZ0po.js → sequenceDiagram-WJ2MYXX4-BI3gSsUa.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BI3gSsUa.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BI3gSsUa.js.gz +0 -0
- package/dist/client/assets/settings-2-GFFo5UwO.js +1 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-Dz1ykd8a.js → sizeCapture-INFHLROL-NBzIIRER.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-NBzIIRER.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-NBzIIRER.js.gz +0 -0
- package/dist/client/assets/{sparkles-0HeBLLwx.js → sparkles-ZXBp-KvJ.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-DHfjTfLj.js → stateDiagram-D77RDMKH-D-D0FB4D.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-D-D0FB4D.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-D-D0FB4D.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-Dk-QpCnJ.js +1 -0
- package/dist/client/assets/sun-CO5F9zS4.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-CAJkSUVz.js → swimlanes-42K2YHIH-CH_jxbV3.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-CH_jxbV3.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-CH_jxbV3.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-C4E5FOdv.js +8 -0
- package/dist/client/assets/swiper-DJjq1TYT.js +1 -0
- 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-C9PKWUqs.js +4 -0
- package/dist/client/assets/syntaxHighlight-C9PKWUqs.js.br +0 -0
- package/dist/client/assets/syntaxHighlight-C9PKWUqs.js.gz +0 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-plr9yv12.js → timeline-definition-24CTP7MA-DuiaCBON.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-DuiaCBON.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-DuiaCBON.js.gz +0 -0
- package/dist/client/assets/triangle-alert-CuGUScPU.js +1 -0
- package/dist/client/assets/unplug-C0pb-BKU.js +1 -0
- package/dist/client/assets/use-gesture-react.esm-DYpdiE_k.js +1 -0
- 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/v4-EwEgHOG0.js +1 -0
- package/dist/client/assets/{vennDiagram-4TSXK5OY-Cz04de3S.js → vennDiagram-4TSXK5OY-DUEZFEzo.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-DUEZFEzo.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-DUEZFEzo.js.gz +0 -0
- package/dist/client/assets/videoCompression-JAcp0atl.js +3 -0
- package/dist/client/assets/videoCompression-JAcp0atl.js.br +0 -0
- package/dist/client/assets/videoCompression-JAcp0atl.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-DnT-q6At.js → wardleyDiagram-VM6X3IG4-CnHmIbot.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-CnHmIbot.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-CnHmIbot.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-C0y08MEX.js → xychartDiagram-S5SC5T6Z-CDAoHQTr.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-CDAoHQTr.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-CDAoHQTr.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/collaborationDeliveryWorker.js +11 -0
- package/dist/server/agent/collaborationPrompt.js +4 -2
- package/dist/server/agent/collaborationStore.js +7 -1
- package/dist/server/agent/sessionAttachmentRoutes.js +39 -0
- package/dist/server/agent/sessionChatRoutes.js +64 -0
- package/dist/server/agent/sessionViewModeRoutes.js +72 -0
- package/dist/server/android/recording.js +19 -17
- package/dist/server/cli.js +34 -0
- package/dist/server/entry.js +25 -17
- package/dist/server/federation/accessPolicy.js +8 -3
- package/dist/server/federation/authorization.js +26 -3
- package/dist/server/federation/invitations.js +27 -6
- package/dist/server/federation/requestContext.js +5 -1
- package/dist/server/federation/resourceAccess.js +63 -0
- package/dist/server/federation/runtime.js +60 -17
- package/dist/server/federation/sessionSharing.js +39 -0
- package/dist/server/routes/android.js +36 -7
- package/dist/server/routes/computer.js +14 -4
- package/dist/server/routes/filesystem.js +6 -3
- package/dist/server/routes/rdp.js +4 -3
- package/dist/server/routes/sharedFiles.js +51 -0
- package/dist/server/routes/terminal.js +83 -14
- package/dist/server/routes/vncReadOnly.js +134 -0
- package/dist/server/shared/shareResources.js +37 -0
- package/dist/server/todos/cli.js +91 -0
- package/dist/server/todos/routes.js +73 -0
- package/dist/server/todos/store.js +240 -0
- package/dist/server/todos/types.js +1 -0
- package/dist/server/utils/autoUpdate.js +7 -2
- package/dist/server/utils/clientSnapshotMaintenance.js +200 -0
- package/dist/server/utils/runtimeClient.js +79 -0
- package/dist/server/utils/serverCleanup.js +24 -0
- package/dist/server/utils/storageMaintenance.js +60 -0
- package/dist/server/utils/workspaceCacheMaintenance.js +158 -0
- package/package.json +1 -1
- package/runtime-manifest.json +3 -3
- package/dist/client/assets/App-BiDKeTpx.js +0 -187
- package/dist/client/assets/App-BiDKeTpx.js.br +0 -0
- package/dist/client/assets/App-BiDKeTpx.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-DfRsqSBx.js +0 -9
- package/dist/client/assets/ArchitecturePanel-DfRsqSBx.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-DfRsqSBx.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DksecDp5.js +0 -2
- package/dist/client/assets/ChangeWalkthroughPanel-DksecDp5.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DksecDp5.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-D-TgDrxX.js +0 -1
- package/dist/client/assets/CollaborationMainWorkspace-D-TgDrxX.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-D-TgDrxX.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-Ce6gFw26.js +0 -2
- package/dist/client/assets/ComputerControlView-BKwuceeG.js +0 -6
- package/dist/client/assets/ComputerControlView-BKwuceeG.js.br +0 -0
- package/dist/client/assets/ComputerControlView-BKwuceeG.js.gz +0 -0
- package/dist/client/assets/DagPlayground-Cshoqo5l.js +0 -1
- package/dist/client/assets/DagPlayground-Cshoqo5l.js.br +0 -0
- package/dist/client/assets/DagPlayground-Cshoqo5l.js.gz +0 -0
- package/dist/client/assets/DiffLab-DFut8ZHi.js.br +0 -0
- package/dist/client/assets/DiffLab-DFut8ZHi.js.gz +0 -0
- package/dist/client/assets/DiffReview-DkEi5c3c.js +0 -1
- package/dist/client/assets/DiffReview-DkEi5c3c.js.br +0 -0
- package/dist/client/assets/DiffReview-DkEi5c3c.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-Y9JlI5DQ.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-Y9JlI5DQ.js.gz +0 -0
- package/dist/client/assets/DiffViewer-3JGF6U0m.js +0 -37
- package/dist/client/assets/DiffViewer-3JGF6U0m.js.br +0 -0
- package/dist/client/assets/DiffViewer-3JGF6U0m.js.gz +0 -0
- package/dist/client/assets/ModelPreview-Cv6JgunE.js.br +0 -0
- package/dist/client/assets/ModelPreview-Cv6JgunE.js.gz +0 -0
- package/dist/client/assets/RightSidebar-akYx_nRq.js +0 -1679
- package/dist/client/assets/RightSidebar-akYx_nRq.js.br +0 -0
- package/dist/client/assets/RightSidebar-akYx_nRq.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-CcCffdpj.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-CcCffdpj.js.gz +0 -0
- package/dist/client/assets/app-BB0CAQ8D.js +0 -35
- package/dist/client/assets/app-BB0CAQ8D.js.br +0 -0
- package/dist/client/assets/app-BB0CAQ8D.js.gz +0 -0
- package/dist/client/assets/arc-CXcCuzb3.js.br +0 -0
- package/dist/client/assets/arc-CXcCuzb3.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CmR7whZz.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CmR7whZz.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-DODhUpHm.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-DODhUpHm.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-BF9eEb2Y.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-BF9eEb2Y.js.gz +0 -0
- package/dist/client/assets/channel-BKkTA-X_.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-Bv45U-az.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CfEErbEd.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CfEErbEd.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-CbyJW-OT.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-CbyJW-OT.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-t93EivmS.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-t93EivmS.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-D9nwA51-.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-D9nwA51-.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-CksG0bvP.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-CksG0bvP.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-CV3T078G.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-CV3T078G.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BQnJabEs.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BQnJabEs.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-BeIQp5Pv.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-BeIQp5Pv.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-D-Mibxiq.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-D-Mibxiq.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-eiIMWp9t.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-eiIMWp9t.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CTVOWtNL.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CTVOWtNL.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-NKKk6uh7.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-NKKk6uh7.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-DKYdBuNs.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-DKYdBuNs.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-DaM3D5Ed.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-DaM3D5Ed.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-Df9kQwWg.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-Df9kQwWg.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-CWjDvpsN.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-CWjDvpsN.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-mKt-CgrH.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-mKt-CgrH.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-C8TBuoU3.js +0 -1
- package/dist/client/assets/gestureArbiter-C8TBuoU3.js.br +0 -0
- package/dist/client/assets/gestureArbiter-C8TBuoU3.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BqnaVVZ0.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BqnaVVZ0.js.gz +0 -0
- package/dist/client/assets/index-3lPto_1X.css +0 -1
- package/dist/client/assets/index-3lPto_1X.css.br +0 -0
- package/dist/client/assets/index-3lPto_1X.css.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CKo9urjS.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CKo9urjS.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-Cd4jK-oI.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-Cd4jK-oI.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BuulZGO-.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BuulZGO-.js.gz +0 -0
- package/dist/client/assets/layout-BpJ2ofrz.js.gz +0 -0
- package/dist/client/assets/linear-CaSbGLIA.js.br +0 -0
- package/dist/client/assets/linear-CaSbGLIA.js.gz +0 -0
- package/dist/client/assets/mermaid.core-BlkorXd6.js.br +0 -0
- package/dist/client/assets/mermaid.core-BlkorXd6.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BqKhK1NJ.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BqKhK1NJ.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-DLUamTjd.js +0 -2
- package/dist/client/assets/passwordBootstrap-DLUamTjd.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-DLUamTjd.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BlWgUklx.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BlWgUklx.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-Bp32kHHQ.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-Bp32kHHQ.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-0qeSh9-W.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-0qeSh9-W.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-COmWghVD.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-COmWghVD.js.gz +0 -0
- package/dist/client/assets/rdpSession-Bbycnmly.js +0 -1
- package/dist/client/assets/rdpSession-Bbycnmly.js.br +0 -0
- package/dist/client/assets/rdpSession-Bbycnmly.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-CQ9ote5C.js +0 -1
- package/dist/client/assets/referenceLongPress-CQ9ote5C.js.br +0 -0
- package/dist/client/assets/referenceLongPress-CQ9ote5C.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-CeohDJAC.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-CeohDJAC.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-rA5x9iOw.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-rA5x9iOw.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Cw2BZ0po.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Cw2BZ0po.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-Dz1ykd8a.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-Dz1ykd8a.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-DHfjTfLj.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-DHfjTfLj.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-B9AqPaxQ.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-CAJkSUVz.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-CAJkSUVz.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DPbqRuWV.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-plr9yv12.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-plr9yv12.js.gz +0 -0
- package/dist/client/assets/triangle-alert-ibl6FV9I.js +0 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-Cz04de3S.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-Cz04de3S.js.gz +0 -0
- package/dist/client/assets/videoCompression-DCsUD6X6.js +0 -3
- package/dist/client/assets/videoCompression-DCsUD6X6.js.br +0 -0
- package/dist/client/assets/videoCompression-DCsUD6X6.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DnT-q6At.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DnT-q6At.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-C0y08MEX.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-C0y08MEX.js.gz +0 -0
- /package/dist/client/assets/{gestureArbiter-C2J0D0kV.css → swiper-C2J0D0kV.css} +0 -0
- /package/dist/client/assets/{gestureArbiter-C2J0D0kV.css.br → swiper-C2J0D0kV.css.br} +0 -0
- /package/dist/client/assets/{gestureArbiter-C2J0D0kV.css.gz → swiper-C2J0D0kV.css.gz} +0 -0
package/dist/server/entry.js
CHANGED
|
@@ -52,10 +52,12 @@ import { isAllowedHost, isUpgradeOriginAllowed, validateHostMiddleware, validate
|
|
|
52
52
|
import { getCookieSecurityOptions, setSecureCookieMode } from './utils/cookieSecurity.js';
|
|
53
53
|
import { requestDeadlineMiddleware } from './utils/requestDeadline.js';
|
|
54
54
|
import { RuntimeMonitor } from './utils/runtimeMonitor.js';
|
|
55
|
+
import { serverCleanup } from './utils/serverCleanup.js';
|
|
55
56
|
import { startOnboardingServer, stopOnboardingServer, getOnboardingServerUrl } from './onboardingServer.js';
|
|
56
57
|
import { CertificateWatcher } from './certificateWatcher.js';
|
|
57
58
|
import { startTermdockLogMaintenance, writeDiffTraceLog, writeErrorLog, writeJsonLog, writeTextLog, } from './utils/serverLogger.js';
|
|
58
|
-
import { indexRetainedClientModules, pinBundledRuntimeClientDist } from './utils/runtimeClient.js';
|
|
59
|
+
import { indexRetainedClientModules, pinBundledRuntimeClientDist, resolveRetainedClientModule } from './utils/runtimeClient.js';
|
|
60
|
+
import { startGeneratedStorageMaintenance } from './utils/storageMaintenance.js';
|
|
59
61
|
import { installCrashForensics } from './utils/crashForensics.js';
|
|
60
62
|
import { getTermdockVersion, TERMDOCK_CAPABILITIES, TERMDOCK_PROTOCOL_VERSION, } from './utils/version.js';
|
|
61
63
|
import { PORT, DEFAULT_HOST } from './config.js';
|
|
@@ -363,6 +365,7 @@ export function createApp(options = {}) {
|
|
|
363
365
|
app.use('/api/computer', computerRoutes);
|
|
364
366
|
if (fs.existsSync(bundledClientIndexPath)) {
|
|
365
367
|
const clientPath = pinBundledRuntimeClientDist(bundledClientDistPath);
|
|
368
|
+
app.locals.runtimeClientPath = clientPath;
|
|
366
369
|
const compression = createStaticCompressionMiddleware(clientPath);
|
|
367
370
|
const staticFiles = express.static(clientPath, {
|
|
368
371
|
setHeaders: (res, filePath, stat) => {
|
|
@@ -395,7 +398,7 @@ export function createApp(options = {}) {
|
|
|
395
398
|
});
|
|
396
399
|
const retainedModules = indexRetainedClientModules();
|
|
397
400
|
app.get(/^\/assets\//, (req, res, next) => {
|
|
398
|
-
const retainedPath = retainedModules
|
|
401
|
+
const retainedPath = resolveRetainedClientModule(retainedModules, req.path);
|
|
399
402
|
if (!retainedPath) {
|
|
400
403
|
// Never cache a missing module or return the SPA HTML as JavaScript.
|
|
401
404
|
res.setHeader('Cache-Control', 'no-store');
|
|
@@ -403,7 +406,7 @@ export function createApp(options = {}) {
|
|
|
403
406
|
return;
|
|
404
407
|
}
|
|
405
408
|
setStaticCacheHeaders(req, res);
|
|
406
|
-
res.sendFile(retainedPath, error => {
|
|
409
|
+
res.sendFile(path.basename(retainedPath), { root: path.dirname(retainedPath) }, error => {
|
|
407
410
|
if (!error)
|
|
408
411
|
return;
|
|
409
412
|
if (res.headersSent)
|
|
@@ -414,7 +417,7 @@ export function createApp(options = {}) {
|
|
|
414
417
|
});
|
|
415
418
|
app.get(/^(?!\/api(?:\/|$)|\/health$|\/onboarding(?:\/|$)|\/ca(?:\/|$)).*/, (req, res) => {
|
|
416
419
|
setStaticCacheHeaders(req, res);
|
|
417
|
-
res.sendFile(
|
|
420
|
+
res.sendFile('index.html', { root: clientPath });
|
|
418
421
|
});
|
|
419
422
|
}
|
|
420
423
|
// Never expose parser errors, submitted passwords, or stack traces to callers.
|
|
@@ -471,9 +474,14 @@ export function startServer(options = {}) {
|
|
|
471
474
|
runtimeMonitor,
|
|
472
475
|
});
|
|
473
476
|
const { server, scheme } = createServerForApp(app, options);
|
|
477
|
+
const onServerClose = serverCleanup(server);
|
|
478
|
+
if (typeof app.locals.runtimeClientPath === 'string')
|
|
479
|
+
onServerClose(startGeneratedStorageMaintenance({
|
|
480
|
+
stateDirectory: path.join(homedir(), '.termdock'), clientDirectory: app.locals.runtimeClientPath,
|
|
481
|
+
}));
|
|
474
482
|
let webTransport;
|
|
475
483
|
const webTransportDescriptor = () => webTransport?.descriptor();
|
|
476
|
-
|
|
484
|
+
onServerClose(() => webTransport?.close());
|
|
477
485
|
const collaborationChannels = new Map();
|
|
478
486
|
const reverseCollaboration = (id) => [...(collaborationChannels.get(id) ?? [])].find(rpc => !rpc.closed);
|
|
479
487
|
let collaborationService;
|
|
@@ -512,11 +520,11 @@ export function startServer(options = {}) {
|
|
|
512
520
|
refreshDirectTargets();
|
|
513
521
|
const desktopRoutesTimer = setInterval(refreshDirectTargets, 2000);
|
|
514
522
|
desktopRoutesTimer.unref();
|
|
515
|
-
|
|
523
|
+
onServerClose(() => { clearInterval(desktopRoutesTimer); for (const target of dynamicDirectTargets.values())
|
|
516
524
|
target.close(); });
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
525
|
+
onServerClose(() => relayRouter.close());
|
|
526
|
+
onServerClose(() => { void stopAllScrcpySessions(); void androidRecordings.stopAll(); });
|
|
527
|
+
onServerClose(stopComputerBridges);
|
|
520
528
|
const federation = createFederationRuntime(app, path.join(homedir(), '.termdock', 'federation'), {
|
|
521
529
|
terminal: handleTerminalWebSocket, control: handleControlWebSocket, android: handleAndroidWebSocket, computer: handleComputerWebSocket,
|
|
522
530
|
}, {
|
|
@@ -655,7 +663,7 @@ export function startServer(options = {}) {
|
|
|
655
663
|
messages: collaborationStore, taskStore: collaborationTaskStore, tasks: collaborationTasks, peers: collaborationService, sessions: integrationSessionAdapter });
|
|
656
664
|
await integration.listen();
|
|
657
665
|
app.locals.integrationRuntime = integration;
|
|
658
|
-
|
|
666
|
+
onServerClose(() => { void integration?.close(); });
|
|
659
667
|
}
|
|
660
668
|
catch {
|
|
661
669
|
await integration?.close();
|
|
@@ -663,11 +671,11 @@ export function startServer(options = {}) {
|
|
|
663
671
|
}
|
|
664
672
|
}
|
|
665
673
|
collaborationTasks.start();
|
|
666
|
-
|
|
674
|
+
onServerClose(() => collaborationTasks.close());
|
|
667
675
|
collaborationService.start();
|
|
668
|
-
|
|
676
|
+
onServerClose(() => collaborationService?.close());
|
|
669
677
|
collaborationTransport.start();
|
|
670
|
-
|
|
678
|
+
onServerClose(() => collaborationTransport?.close());
|
|
671
679
|
entrySubjectAllowed = subjectId => runtime.store.authorize({ subjectId, serviceId: runtime.serviceId, action: 'authorization.manage' }).allowed;
|
|
672
680
|
routeInvitations = new RouteInvitationStore({
|
|
673
681
|
filePath: path.join(homedir(), '.termdock', 'federation', 'route-invitations.json'), serviceId: runtime.serviceId,
|
|
@@ -688,10 +696,10 @@ export function startServer(options = {}) {
|
|
|
688
696
|
localOrigin: `${scheme}://${localAuthority}:${port}`, ...(localCa ? { localCa } : {}),
|
|
689
697
|
});
|
|
690
698
|
selfRelay.start();
|
|
691
|
-
|
|
699
|
+
onServerClose(() => selfRelay.close());
|
|
692
700
|
}).catch(() => console.error('Route invitations unavailable; uninitialized route grants remain denied.'));
|
|
693
701
|
void federation.catch(() => console.error('Encrypted access initialization failed; business access remains closed.'));
|
|
694
|
-
|
|
702
|
+
onServerClose(() => { void federation.then(runtime => runtime.close()).catch(() => { }); });
|
|
695
703
|
server.headersTimeout = 10_000;
|
|
696
704
|
server.requestTimeout = 120_000;
|
|
697
705
|
server.keepAliveTimeout = 5_000;
|
|
@@ -699,7 +707,7 @@ export function startServer(options = {}) {
|
|
|
699
707
|
server.maxRequestsPerSocket = 1000;
|
|
700
708
|
server.maxConnections = 1024;
|
|
701
709
|
server.on('connection', (socket) => runtimeMonitor.trackSocket(socket));
|
|
702
|
-
|
|
710
|
+
onServerClose(() => runtimeMonitor.stop());
|
|
703
711
|
setSecureCookieMode(scheme === 'https');
|
|
704
712
|
const certWatcher = new CertificateWatcher({
|
|
705
713
|
enabled: scheme === 'https' && Boolean(options.httpsCertPath && options.httpsKeyPath),
|
|
@@ -865,7 +873,7 @@ export function startServer(options = {}) {
|
|
|
865
873
|
console.error('Server error:', error);
|
|
866
874
|
process.exit(1);
|
|
867
875
|
});
|
|
868
|
-
|
|
876
|
+
onServerClose(() => {
|
|
869
877
|
stopLogMaintenance();
|
|
870
878
|
crashForensics.dispose();
|
|
871
879
|
certWatcher.stop();
|
|
@@ -90,13 +90,18 @@ export function resolveAccessRequest(method, path) {
|
|
|
90
90
|
return { action: 'file.read' };
|
|
91
91
|
if (method === 'POST' && route === 'create')
|
|
92
92
|
return { action: 'service.create-session' };
|
|
93
|
-
const
|
|
93
|
+
const attachment = /^([^/]+)\/chat\/attachments$/.exec(route);
|
|
94
|
+
if (method === 'POST' && attachment && !reservedSessionIds.has(attachment[1].toLowerCase()))
|
|
95
|
+
return { action: 'session.input', sessionId: attachment[1] };
|
|
96
|
+
const session = /^([^/]+)(?:\/(health|attach|stream|input|resize|tmux|restart|agent-resume|chat|view-mode))?$/.exec(route);
|
|
94
97
|
if (session && !reservedSessionIds.has(session[1].toLowerCase())) {
|
|
95
98
|
const sessionId = session[1];
|
|
96
99
|
const op = session[2];
|
|
97
|
-
if (read && ['health', 'attach', 'stream', 'agent-resume'].includes(op))
|
|
100
|
+
if (read && ['health', 'attach', 'stream', 'agent-resume', 'chat', 'view-mode'].includes(op))
|
|
98
101
|
return { action: 'session.view', sessionId };
|
|
99
|
-
if (method === '
|
|
102
|
+
if (method === 'PUT' && op === 'view-mode')
|
|
103
|
+
return { action: 'session.view', sessionId };
|
|
104
|
+
if (method === 'POST' && (op === 'input' || op === 'chat'))
|
|
100
105
|
return { action: 'session.input', sessionId };
|
|
101
106
|
if (method === 'POST' && op === 'resize')
|
|
102
107
|
return { action: 'session.resize', sessionId };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validShareResources, resourceSubset } from '../shared/shareResources.js';
|
|
1
2
|
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { mkdirSync, readFileSync, openSync, writeFileSync, fsyncSync, closeSync, renameSync, unlinkSync } from 'node:fs';
|
|
3
4
|
import { dirname } from 'node:path';
|
|
@@ -18,6 +19,7 @@ function validateInput(v) {
|
|
|
18
19
|
assert(v.scope.kind === 'sessions' && ids(v.scope.sessionIds), 'session scope');
|
|
19
20
|
keys(v.scope, ['kind', 'sessionIds']);
|
|
20
21
|
}
|
|
22
|
+
assert(v.resources === undefined || validShareResources(v.resources), 'shared resources');
|
|
21
23
|
assert(Array.isArray(v.actions) && v.actions.length > 0 && v.actions.length <= 256 && v.actions.every(action) && new Set(v.actions).size === v.actions.length, 'actions');
|
|
22
24
|
if (v.scope.kind === 'sessions')
|
|
23
25
|
assert(v.actions.every(a => a.startsWith('session.')), 'session scope cannot grant service or file capabilities');
|
|
@@ -37,7 +39,7 @@ function isSubset(child, parent) {
|
|
|
37
39
|
return false;
|
|
38
40
|
if (parent.scope.kind === 'sessions' && (child.scope.kind !== 'sessions' || !child.scope.sessionIds.every(s => parent.scope.kind === 'sessions' && parent.scope.sessionIds.includes(s))))
|
|
39
41
|
return false;
|
|
40
|
-
if (!parent.actions.includes('service:*') && !child.actions.every(a => parent.actions.includes(a)))
|
|
42
|
+
if (!parent.actions.includes('service:*') && (!child.actions.every(a => parent.actions.includes(a)) || !resourceSubset(child.resources, parent.resources)))
|
|
41
43
|
return false;
|
|
42
44
|
for (const key of ['entryServiceIds', 'relayIds']) {
|
|
43
45
|
const limit = parent.conditions?.[key];
|
|
@@ -86,7 +88,7 @@ export class AuthorizationStore {
|
|
|
86
88
|
for (const value of data.grants) {
|
|
87
89
|
validateInput(value);
|
|
88
90
|
const g = value;
|
|
89
|
-
keys(g, ['id', 'serviceId', 'createdAt', 'parentGrantId', 'revokedAt', 'subjectId', 'scope', 'actions', 'expiresAt', 'canDelegate', 'conditions', 'passwordCredential']);
|
|
91
|
+
keys(g, ['id', 'serviceId', 'createdAt', 'parentGrantId', 'revokedAt', 'subjectId', 'scope', 'actions', 'expiresAt', 'canDelegate', 'conditions', 'passwordCredential', 'resources']);
|
|
90
92
|
assert(id(g.id) && g.serviceId === this.options.serviceId && Number.isSafeInteger(g.createdAt) && g.createdAt >= 0, 'grant metadata');
|
|
91
93
|
assert(g.parentGrantId === undefined || id(g.parentGrantId), 'parent identity');
|
|
92
94
|
assert(g.revokedAt === undefined || (Number.isSafeInteger(g.revokedAt) && g.revokedAt >= 0), 'revocation');
|
|
@@ -122,7 +124,7 @@ export class AuthorizationStore {
|
|
|
122
124
|
}
|
|
123
125
|
create(input) {
|
|
124
126
|
validateInput(input);
|
|
125
|
-
keys(input, ['subjectId', 'scope', 'actions', 'expiresAt', 'canDelegate', 'conditions']);
|
|
127
|
+
keys(input, ['subjectId', 'scope', 'actions', 'expiresAt', 'canDelegate', 'conditions', 'resources']);
|
|
126
128
|
const now = this.now();
|
|
127
129
|
assert(input.expiresAt === undefined || input.expiresAt > now, 'already expired');
|
|
128
130
|
return { ...structuredClone(input), id: randomUUID(), serviceId: this.options.serviceId, createdAt: now };
|
|
@@ -169,6 +171,27 @@ export class AuthorizationStore {
|
|
|
169
171
|
return true;
|
|
170
172
|
}
|
|
171
173
|
list() { return this.read(); }
|
|
174
|
+
listActive() { const grants = this.read(); return grants.filter(grant => active(grant, grants, this.now(), new Set(), this.options.passwordCredential?.())); }
|
|
175
|
+
/** Narrow only this Session, preserving sibling Session grants and descendant checks. */
|
|
176
|
+
revokeSession(sessionId, normalize = id => id) {
|
|
177
|
+
const grants = this.read();
|
|
178
|
+
let changed = 0;
|
|
179
|
+
for (const grant of grants) {
|
|
180
|
+
if (grant.revokedAt !== undefined || grant.scope.kind !== 'sessions')
|
|
181
|
+
continue;
|
|
182
|
+
const remaining = grant.scope.sessionIds.filter(id => normalize(id) !== sessionId);
|
|
183
|
+
if (remaining.length === grant.scope.sessionIds.length)
|
|
184
|
+
continue;
|
|
185
|
+
changed++;
|
|
186
|
+
if (!remaining.length)
|
|
187
|
+
grant.revokedAt = this.now();
|
|
188
|
+
else
|
|
189
|
+
grant.scope = { kind: 'sessions', sessionIds: remaining };
|
|
190
|
+
}
|
|
191
|
+
if (changed)
|
|
192
|
+
this.save(grants);
|
|
193
|
+
return changed;
|
|
194
|
+
}
|
|
172
195
|
listEffective(request) {
|
|
173
196
|
if (!request || !id(request.subjectId) || request.serviceId !== this.options.serviceId)
|
|
174
197
|
return [];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validShareResources } from '../shared/shareResources.js';
|
|
1
2
|
import { createHash, randomBytes, randomUUID } from 'node:crypto';
|
|
2
3
|
import { mkdirSync, readFileSync, openSync, writeFileSync, fsyncSync, closeSync, renameSync, unlinkSync, chmodSync } from 'node:fs';
|
|
3
4
|
import { dirname } from 'node:path';
|
|
@@ -8,8 +9,9 @@ const record = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
|
8
9
|
function requireValid(ok) { if (!ok)
|
|
9
10
|
throw new Error('INVALID_INVITATION'); }
|
|
10
11
|
export function validateInvitation(input) {
|
|
11
|
-
requireValid(record(input) && Object.keys(input).every(k => ['scope', 'actions', 'label', 'expiresAt'].includes(k)));
|
|
12
|
+
requireValid(record(input) && Object.keys(input).every(k => ['scope', 'actions', 'label', 'expiresAt', 'resources'].includes(k)));
|
|
12
13
|
requireValid(record(input.scope) && Array.isArray(input.actions) && input.actions.length > 0 && new Set(input.actions).size === input.actions.length);
|
|
14
|
+
requireValid(input.resources === undefined || validShareResources(input.resources));
|
|
13
15
|
const scope = input.scope;
|
|
14
16
|
if (scope.kind === 'service') {
|
|
15
17
|
requireValid(Object.keys(scope).length === 1 && ((input.actions.length === 1 && input.actions[0] === 'service:*') || input.actions.every(a => typeof a === 'string' && actions.has(a))));
|
|
@@ -50,9 +52,9 @@ export class InvitationStore {
|
|
|
50
52
|
requireValid(record(data) && data.version === 1 && data.serviceId === this.options.serviceId && Array.isArray(data.invitations) && data.invitations.length <= 2048);
|
|
51
53
|
const seen = new Set();
|
|
52
54
|
for (const item of data.invitations) {
|
|
53
|
-
requireValid(record(item) && Object.keys(item).every(k => ['scope', 'actions', 'label', 'expiresAt', 'hash', 'issuerId', 'createdAt', 'invitationExpiresAt', 'consumedAt', 'consumedBy'].includes(k)));
|
|
54
|
-
const { scope, actions, label, expiresAt, hash, issuerId, createdAt, invitationExpiresAt, consumedAt, consumedBy } = item;
|
|
55
|
-
validateInvitation({ scope, actions, ...(label === undefined ? {} : { label }), ...(expiresAt === undefined ? {} : { expiresAt }) });
|
|
55
|
+
requireValid(record(item) && Object.keys(item).every(k => ['scope', 'actions', 'label', 'expiresAt', 'hash', 'issuerId', 'createdAt', 'invitationExpiresAt', 'consumedAt', 'consumedBy', 'resources'].includes(k)));
|
|
56
|
+
const { scope, actions, resources, label, expiresAt, hash, issuerId, createdAt, invitationExpiresAt, consumedAt, consumedBy } = item;
|
|
57
|
+
validateInvitation({ scope, actions, ...(resources === undefined ? {} : { resources }), ...(label === undefined ? {} : { label }), ...(expiresAt === undefined ? {} : { expiresAt }) });
|
|
56
58
|
requireValid(typeof hash === 'string' && /^[a-f0-9]{64}$/.test(hash) && !seen.has(hash) && identity(issuerId));
|
|
57
59
|
seen.add(hash);
|
|
58
60
|
requireValid(Number.isSafeInteger(createdAt) && Number.isSafeInteger(invitationExpiresAt) && Number(invitationExpiresAt) === Number(createdAt) + TTL);
|
|
@@ -87,7 +89,7 @@ export class InvitationStore {
|
|
|
87
89
|
listPending() {
|
|
88
90
|
const now = this.now();
|
|
89
91
|
return this.read().filter(i => i.consumedAt === undefined && i.invitationExpiresAt > now && (i.expiresAt === undefined || i.expiresAt > now))
|
|
90
|
-
.map(({ hash, scope, actions, label, expiresAt, createdAt, invitationExpiresAt }) => ({ id: hash, scope, actions, ...(label ? { label } : {}), ...(expiresAt === undefined ? {} : { expiresAt }), createdAt, invitationExpiresAt }));
|
|
92
|
+
.map(({ hash, scope, actions, resources, label, expiresAt, createdAt, invitationExpiresAt }) => ({ id: hash, scope, actions, ...(resources === undefined ? {} : { resources }), ...(label ? { label } : {}), ...(expiresAt === undefined ? {} : { expiresAt }), createdAt, invitationExpiresAt }));
|
|
91
93
|
}
|
|
92
94
|
revoke(id) {
|
|
93
95
|
const current = this.read();
|
|
@@ -98,6 +100,25 @@ export class InvitationStore {
|
|
|
98
100
|
this.save(current);
|
|
99
101
|
return true;
|
|
100
102
|
}
|
|
103
|
+
revokeSession(sessionId, normalize = id => id) {
|
|
104
|
+
const current = this.read();
|
|
105
|
+
let changed = 0;
|
|
106
|
+
const next = current.filter(invitation => {
|
|
107
|
+
if (invitation.consumedAt !== undefined || invitation.scope.kind !== 'sessions')
|
|
108
|
+
return true;
|
|
109
|
+
const remaining = invitation.scope.sessionIds.filter(id => normalize(id) !== sessionId);
|
|
110
|
+
if (remaining.length === invitation.scope.sessionIds.length)
|
|
111
|
+
return true;
|
|
112
|
+
changed++;
|
|
113
|
+
if (!remaining.length)
|
|
114
|
+
return false;
|
|
115
|
+
invitation.scope = { kind: 'sessions', sessionIds: remaining };
|
|
116
|
+
return true;
|
|
117
|
+
});
|
|
118
|
+
if (changed)
|
|
119
|
+
this.save(next);
|
|
120
|
+
return changed;
|
|
121
|
+
}
|
|
101
122
|
create(issuerId, input) {
|
|
102
123
|
requireValid(identity(issuerId));
|
|
103
124
|
validateInvitation(input);
|
|
@@ -129,7 +150,7 @@ export class InvitationStore {
|
|
|
129
150
|
invite.consumedAt = now;
|
|
130
151
|
invite.consumedBy = subjectId;
|
|
131
152
|
this.save(current);
|
|
132
|
-
return { subjectId, scope: structuredClone(invite.scope), actions: [...invite.actions], ...(invite.expiresAt === undefined ? {} : { expiresAt: invite.expiresAt }), canDelegate: false };
|
|
153
|
+
return { subjectId, ...(invite.resources ? { resources: structuredClone(invite.resources) } : {}), scope: structuredClone(invite.scope), actions: [...invite.actions], ...(invite.expiresAt === undefined ? {} : { expiresAt: invite.expiresAt }), canDelegate: false };
|
|
133
154
|
}
|
|
134
155
|
subjectLabels() {
|
|
135
156
|
return Object.fromEntries(this.read().filter(i => i.consumedBy && i.label).map(i => [i.consumedBy, i.label]));
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
// Object identity is deliberately used instead of a remotely forgeable header.
|
|
2
|
+
const resources = new WeakMap();
|
|
3
|
+
export function encryptedRequestResources(request) { return resources.get(request); }
|
|
2
4
|
const verified = new WeakSet();
|
|
3
5
|
const subjects = new WeakMap();
|
|
4
6
|
const serviceAccess = new WeakSet();
|
|
5
|
-
export function markEncryptedRequest(request, subjectId, fullService = false) {
|
|
7
|
+
export function markEncryptedRequest(request, subjectId, fullService = false, shared) {
|
|
6
8
|
verified.add(request);
|
|
9
|
+
if (shared)
|
|
10
|
+
resources.set(request, shared);
|
|
7
11
|
if (subjectId)
|
|
8
12
|
subjects.set(request, subjectId);
|
|
9
13
|
if (fullService)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { realpathSync, statSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { canonicalApiPath } from './accessPolicy.js';
|
|
4
|
+
export function computerResource(resources, host, protocol = 'vnc', port = protocol === 'rdp' ? 3389 : 5900) {
|
|
5
|
+
const matches = resources.computers?.filter(c => c.host.toLowerCase() === host.toLowerCase() && c.protocol === protocol && c.port === port);
|
|
6
|
+
return matches?.find(c => c.access === 'control') ?? matches?.[0];
|
|
7
|
+
}
|
|
8
|
+
/** Check both the lexical and real path. Symlinks cannot widen a shared root. */
|
|
9
|
+
export function directoryResource(resources, requested, write = false) {
|
|
10
|
+
if (!path.isAbsolute(requested) || requested.includes('\0'))
|
|
11
|
+
return false;
|
|
12
|
+
try {
|
|
13
|
+
const lexical = path.resolve(requested), real = realpathSync(lexical);
|
|
14
|
+
const inside = (root, file) => { const rel = path.relative(root, file); return rel === '' || (!rel.startsWith(`..${path.sep}`) && rel !== '..' && !path.isAbsolute(rel)); };
|
|
15
|
+
return resources.directories?.some(root => (!write || root.access === 'write') && inside(path.resolve(root.path), lexical) && inside(realpathSync(root.path), real)) === true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function canonicalShareDirectories(resources) {
|
|
22
|
+
return { ...resources, ...(resources.directories ? { directories: resources.directories.map(root => {
|
|
23
|
+
const canonical = realpathSync(root.path);
|
|
24
|
+
if (!statSync(canonical).isDirectory())
|
|
25
|
+
throw new Error('SHARE_DIRECTORY_UNAVAILABLE');
|
|
26
|
+
return { ...root, path: canonical };
|
|
27
|
+
}) } : {}) };
|
|
28
|
+
}
|
|
29
|
+
export function resourceHttpAllowed(resources, method, requestPath) {
|
|
30
|
+
const pathname = canonicalApiPath(requestPath);
|
|
31
|
+
if (!pathname)
|
|
32
|
+
return false;
|
|
33
|
+
if (method === 'GET' && pathname === '/api/android/devices')
|
|
34
|
+
return !!resources.android?.length;
|
|
35
|
+
if (resources.android?.length && ((['GET', 'POST'].includes(method) && pathname === '/api/android/recordings') || (method === 'POST' && /^\/api\/android\/recordings\/[a-f0-9-]{36}\/(?:stop|save)$/.test(pathname)) || (method === 'DELETE' && /^\/api\/android\/recordings\/[a-f0-9-]{36}$/.test(pathname))))
|
|
36
|
+
return true;
|
|
37
|
+
if (method === 'GET' && pathname === '/api/computer/status')
|
|
38
|
+
return !!resources.computers?.length;
|
|
39
|
+
const url = new URL(requestPath, 'http://inner');
|
|
40
|
+
const op = pathname.slice('/api/terminal/fs/'.length);
|
|
41
|
+
if (['GET', 'HEAD'].includes(method) && op.startsWith('preview/')) {
|
|
42
|
+
const parts = op.slice(8).split('/');
|
|
43
|
+
if (/^[0-9a-f]{32}$/.test(parts[0]))
|
|
44
|
+
parts.shift();
|
|
45
|
+
return directoryResource(resources, '/' + parts.join('/'));
|
|
46
|
+
}
|
|
47
|
+
if (!pathname.startsWith('/api/terminal/fs/'))
|
|
48
|
+
return false;
|
|
49
|
+
// Unregistered Git, shell, native-open and recursive APIs remain unavailable.
|
|
50
|
+
const read = ['GET', 'HEAD'].includes(method) && ['list', 'read', 'blob', 'video', 'download', 'eda-preview', 'eda-inspect'].includes(op);
|
|
51
|
+
const write = (method === 'POST' && op === 'upload') || (method === 'PUT' && op === 'write') || (method === 'DELETE' && op === 'file');
|
|
52
|
+
if (!read && !write)
|
|
53
|
+
return false;
|
|
54
|
+
const key = op === 'upload' ? 'dir' : 'path';
|
|
55
|
+
if (url.searchParams.getAll(key).length !== 1 || !directoryResource(resources, url.searchParams.get(key) ?? '', write))
|
|
56
|
+
return false;
|
|
57
|
+
// Listing can run git check-ignore; its repository is another filesystem input.
|
|
58
|
+
for (const extra of ['gitIgnoreRoot', 'cwd', 'root', 'repoRoot']) {
|
|
59
|
+
if (url.searchParams.has(extra) && (url.searchParams.getAll(extra).length !== 1 || !directoryResource(resources, url.searchParams.get(extra) ?? '')))
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { SessionSharingPresence } from './sessionSharing.js';
|
|
2
|
+
import { collaborationSessionDisplayName } from '../utils/sessionDisplayName.js';
|
|
3
|
+
import { mergeShareResources } from '../shared/shareResources.js';
|
|
4
|
+
import { canonicalShareDirectories, computerResource, resourceHttpAllowed } from './resourceAccess.js';
|
|
1
5
|
import { CollaborationRpc } from '../agent/collaborationPeerTransport.js';
|
|
2
6
|
import { PasswordBootstrapServer } from './passwordBootstrap.js';
|
|
3
7
|
import { DeviceProfiles } from './deviceProfiles.js';
|
|
@@ -92,6 +96,7 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
92
96
|
const passwordStarts = [];
|
|
93
97
|
const passwordAttempts = new Map();
|
|
94
98
|
const store = new AuthorizationStore({ serviceId, filePath: join(directory, 'grants.json'), passwordCredential: getPasswordCredentialFingerprint });
|
|
99
|
+
const sharingPresence = new SessionSharingPresence();
|
|
95
100
|
const deviceProfiles = new DeviceProfiles(join(directory, 'device-profiles.json'));
|
|
96
101
|
const deviceNames = new DeviceNames(join(directory, 'device-names.json'));
|
|
97
102
|
const invitations = new InvitationStore({ serviceId, filePath: join(directory, 'invitations.json') });
|
|
@@ -113,7 +118,9 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
113
118
|
delete request.headers['x-termdock-device'];
|
|
114
119
|
const fullService = request.headers['x-termdock-full-service'] === '1';
|
|
115
120
|
delete request.headers['x-termdock-full-service'];
|
|
116
|
-
|
|
121
|
+
const resourcesHeader = request.headers['x-termdock-resources'];
|
|
122
|
+
delete request.headers['x-termdock-resources'];
|
|
123
|
+
markEncryptedRequest(request, typeof subject === 'string' ? subject : undefined, fullService, typeof resourcesHeader === 'string' ? JSON.parse(resourcesHeader) : undefined);
|
|
117
124
|
app(request, response);
|
|
118
125
|
});
|
|
119
126
|
// The outer encrypted consumer can apply backpressure long after a local
|
|
@@ -163,10 +170,14 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
163
170
|
const stable = stableSessionId(sessionId);
|
|
164
171
|
return stable !== undefined && store.authorize({ subjectId, serviceId, action, sessionId: stable }).allowed;
|
|
165
172
|
};
|
|
173
|
+
const resources = (subjectId) => mergeShareResources(store.listEffective({ subjectId, serviceId }).map(g => g.resources));
|
|
174
|
+
const androidAllowed = (subjectId, serial, control = false) => full(subjectId) || resources(subjectId).android?.some(d => d.serial === serial && (!control || d.access === 'control')) === true;
|
|
175
|
+
const computerAllowed = (subjectId, target) => full(subjectId) || !!computerResource(resources(subjectId), target.host, target.protocol, target.port);
|
|
166
176
|
const full = (subjectId) => open() || store.hasFullServiceAccess({ subjectId, serviceId });
|
|
167
177
|
let channel;
|
|
168
178
|
const operations = new Map();
|
|
169
179
|
const sockets = new Map();
|
|
180
|
+
const presenceKey = randomUUID();
|
|
170
181
|
let revokeTimer;
|
|
171
182
|
try {
|
|
172
183
|
const secured = await secureConnection({ identity, duplex, initiator: false, signal: AbortSignal.timeout(15000) });
|
|
@@ -180,23 +191,26 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
180
191
|
if (typeof head.method !== 'string' || typeof head.path !== 'string' || !isBusinessApiPath(head.method, head.path))
|
|
181
192
|
throw new Error('API_NOT_ALLOWED');
|
|
182
193
|
const policy = resolveAccessRequest(head.method, head.path);
|
|
183
|
-
if (!full(subjectId)) {
|
|
194
|
+
if (!full(subjectId) && !resourceHttpAllowed(resources(subjectId), head.method, head.path)) {
|
|
184
195
|
if (!policy)
|
|
185
196
|
throw new Error('API_NOT_ALLOWED');
|
|
186
197
|
check(policy.action, policy.sessionId);
|
|
187
198
|
}
|
|
188
199
|
};
|
|
189
200
|
async function runHttp(id, operation) {
|
|
201
|
+
const scopeStamp = JSON.stringify([full(subjectId), resources(subjectId)]);
|
|
190
202
|
const sendHttp = (packet) => {
|
|
191
203
|
if (operations.get(id) !== operation || operation.abort.signal.aborted)
|
|
192
204
|
throw new Error('CANCELLED');
|
|
205
|
+
if (scopeStamp !== JSON.stringify([full(subjectId), resources(subjectId)]))
|
|
206
|
+
throw new Error('AUTHORIZATION_CHANGED');
|
|
193
207
|
httpAllowed(operation.head);
|
|
194
208
|
send(packet);
|
|
195
209
|
};
|
|
196
210
|
try {
|
|
197
211
|
httpAllowed(operation.head);
|
|
198
212
|
const headers = new Headers({ 'x-termdock-inner': internalToken, 'x-termdock-device': subjectId,
|
|
199
|
-
'x-termdock-full-service': full(subjectId) ? '1' : '0' });
|
|
213
|
+
'x-termdock-full-service': full(subjectId) ? '1' : '0', 'x-termdock-resources': JSON.stringify(resources(subjectId)) });
|
|
200
214
|
const supplied = operation.head.headers;
|
|
201
215
|
if (supplied && typeof supplied === 'object')
|
|
202
216
|
for (const [name, value] of Object.entries(supplied)) {
|
|
@@ -259,9 +273,9 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
259
273
|
}
|
|
260
274
|
}
|
|
261
275
|
revokeTimer = setInterval(() => {
|
|
262
|
-
for (const { socket: logical, sessionId, android, computer } of sockets.values()) {
|
|
276
|
+
for (const { socket: logical, sessionId, android, computer, computerTarget, computerControl } of sockets.values()) {
|
|
263
277
|
if (android || computer) {
|
|
264
|
-
if (!full(subjectId))
|
|
278
|
+
if (android ? !androidAllowed(subjectId, android) : !computerTarget || !computerAllowed(subjectId, computerTarget) || (computerControl && !full(subjectId) && !resources(subjectId).computers?.some(c => c.host.toLowerCase() === computerTarget.host.toLowerCase() && c.protocol === computerTarget.protocol && c.port === computerTarget.port && c.access === 'control')))
|
|
265
279
|
logical.close(4003, 'Authorization revoked');
|
|
266
280
|
continue;
|
|
267
281
|
}
|
|
@@ -400,11 +414,26 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
400
414
|
return [];
|
|
401
415
|
seen.add(sessionId);
|
|
402
416
|
return [{ sessionId, ...(typeof session.sessionId === 'string' ? { sourceSessionId: session.sessionId } : {}),
|
|
403
|
-
name: typeof session.name === 'string' ? session.name : sessionId, live: session.live === true,
|
|
417
|
+
name: collaborationSessionDisplayName({ ...session, name: typeof session.name === 'string' ? session.name : sessionId }), live: session.live === true,
|
|
404
418
|
canWrite: allowed(subjectId, 'session.input', sessionId), canResize: allowed(subjectId, 'session.resize', sessionId) }];
|
|
405
419
|
});
|
|
406
420
|
send({ type: 'result', id: packet.id, items });
|
|
407
421
|
}
|
|
422
|
+
else if (packet.type === 'session-sharing-status') {
|
|
423
|
+
check('authorization.manage');
|
|
424
|
+
const normalize = (id) => options.resolveSessionId?.(id) || id;
|
|
425
|
+
send({ type: 'result', id: packet.id, items: sharingPresence.summary(store.listActive(), invitations.listPending(), normalize) });
|
|
426
|
+
}
|
|
427
|
+
else if (packet.type === 'session-sharing-stop') {
|
|
428
|
+
check('authorization.manage');
|
|
429
|
+
if (typeof packet.sessionId !== 'string' || !packet.sessionId)
|
|
430
|
+
throw new Error('INVALID_SESSION');
|
|
431
|
+
const normalize = (id) => options.resolveSessionId?.(id) || id;
|
|
432
|
+
const sessionId = normalize(packet.sessionId);
|
|
433
|
+
const revoked = store.revokeSession(sessionId, normalize);
|
|
434
|
+
invitations.revokeSession(sessionId, normalize);
|
|
435
|
+
send({ type: 'result', id: packet.id, revoked });
|
|
436
|
+
}
|
|
408
437
|
else if (packet.type === 'self-relay-ticket') {
|
|
409
438
|
if (!options.issueSelfRelayTicket)
|
|
410
439
|
throw new Error('SELF_RELAY_UNAVAILABLE');
|
|
@@ -461,10 +490,12 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
461
490
|
}
|
|
462
491
|
else if (packet.type === 'invite-create') {
|
|
463
492
|
check('authorization.manage');
|
|
464
|
-
const invitation = { scope: packet.scope, actions: packet.actions,
|
|
493
|
+
const invitation = { scope: packet.scope, actions: packet.actions, ...(packet.resources === undefined ? {} : { resources: packet.resources }),
|
|
465
494
|
...(packet.label === undefined ? {} : { label: packet.label }),
|
|
466
495
|
...(packet.expiresAt === undefined ? {} : { expiresAt: packet.expiresAt }) };
|
|
467
496
|
validateInvitation(invitation);
|
|
497
|
+
if (invitation.resources)
|
|
498
|
+
invitation.resources = canonicalShareDirectories(invitation.resources);
|
|
468
499
|
if (invitation.scope.kind === 'sessions') {
|
|
469
500
|
// Bind to the stable client session id, never the transient backend
|
|
470
501
|
// PTY id: backends are replaced on restart, tmux recovery or
|
|
@@ -524,6 +555,9 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
524
555
|
if ((context.singleOperation ? operations.size + sockets.size >= 1 : operations.size >= MAX_SERVER_HTTP_REQUESTS) || operations.has(packet.id) || sockets.has(packet.id))
|
|
525
556
|
throw new Error('STREAM_LIMIT');
|
|
526
557
|
httpAllowed(packet);
|
|
558
|
+
const requestedSession = resolveAccessRequest(String(packet.method), String(packet.path))?.sessionId;
|
|
559
|
+
if (requestedSession && !full(subjectId))
|
|
560
|
+
sharingPresence.visit(presenceKey, subjectId, stableSessionId(requestedSession) || requestedSession);
|
|
527
561
|
const upload = !['GET', 'HEAD'].includes(String(packet.method));
|
|
528
562
|
if (upload && uploadSlots >= 8)
|
|
529
563
|
throw new Error('UPLOAD_CONCURRENCY_LIMIT');
|
|
@@ -587,26 +621,34 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
587
621
|
const match = /^\/api\/terminal\/([^/%]+)\/ws$/.exec(url.pathname);
|
|
588
622
|
const androidMatch = /^\/api\/android\/([^/%]+)\/ws$/.exec(url.pathname);
|
|
589
623
|
const computer = url.pathname === '/api/computer/ws';
|
|
590
|
-
|
|
624
|
+
const computerTarget = { host: url.searchParams.get('host') ?? '', protocol: url.searchParams.get('protocol') ?? 'vnc', port: Number(url.searchParams.get('port') ?? (url.searchParams.get('protocol') === 'rdp' ? '3389' : '5900')) };
|
|
625
|
+
const canView = () => androidMatch ? androidAllowed(subjectId, androidMatch[1]) : computer ? computerAllowed(subjectId, computerTarget) : allowed(subjectId, match ? 'session.view' : 'service.view', match?.[1]);
|
|
626
|
+
if (match) {
|
|
591
627
|
check('session.view', match[1]);
|
|
628
|
+
if (!full(subjectId))
|
|
629
|
+
sharingPresence.visit(presenceKey, subjectId, stableSessionId(match[1]) || match[1]);
|
|
630
|
+
}
|
|
592
631
|
else if (url.pathname === '/api/control/ws')
|
|
593
632
|
check('service.view');
|
|
594
633
|
else if (androidMatch || computer) {
|
|
595
|
-
if (!
|
|
634
|
+
if (!canView())
|
|
596
635
|
throw new Error('API_NOT_ALLOWED');
|
|
597
636
|
}
|
|
598
637
|
else
|
|
599
638
|
throw new Error('API_NOT_ALLOWED');
|
|
600
|
-
const logical = new LogicalSocket(channel, packet.id,
|
|
601
|
-
sockets.set(packet.id, { socket: logical, sessionId: match?.[1], android:
|
|
602
|
-
computerProtocol: computer ? url.searchParams.get('protocol') ?? 'vnc' : undefined });
|
|
603
|
-
|
|
639
|
+
const logical = new LogicalSocket(channel, packet.id, canView, match ? 1024 * 1024 : undefined, !!match && packet.compression === 'gzip');
|
|
640
|
+
sockets.set(packet.id, { socket: logical, sessionId: match?.[1], android: androidMatch?.[1], computer, computerTarget: computer ? computerTarget : undefined,
|
|
641
|
+
computerControl: computer && (full(subjectId) || resources(subjectId).computers?.some(c => c.host.toLowerCase() === computerTarget.host.toLowerCase() && c.protocol === computerTarget.protocol && c.port === computerTarget.port && c.access === 'control')), computerProtocol: computer ? url.searchParams.get('protocol') ?? 'vnc' : undefined });
|
|
642
|
+
const shareStreamKey = `${presenceKey}:${packet.id}`;
|
|
643
|
+
if (match && !full(subjectId))
|
|
644
|
+
sharingPresence.openStream(shareStreamKey, presenceKey, subjectId, stableSessionId(match[1]) || match[1]);
|
|
645
|
+
logical.once('close', () => { sockets.delete(packet.id); sharingPresence.closeStream(shareStreamKey); });
|
|
604
646
|
send({ type: 'ws-ready', id: packet.id });
|
|
605
647
|
if (match)
|
|
606
648
|
handlers.terminal(logical, match[1], randomUUID(), {
|
|
607
649
|
pushClientId: `device:${subjectId}`, sinceSeq: Math.max(0, Number(url.searchParams.get('since')) || 0), streamEpoch: url.searchParams.get('epoch') ?? undefined,
|
|
608
650
|
flowControl: url.searchParams.get('flow') === '2', outputActive: url.searchParams.get('active') !== '0',
|
|
609
|
-
independentTmux:
|
|
651
|
+
independentTmux: allowed(subjectId, 'session.resize', match[1]) && url.searchParams.get('transport') === 'tmux-client',
|
|
610
652
|
}, allowed(subjectId, 'session.resize', match[1]) ? readTerminalHandshakeDimensions(url.searchParams) : undefined);
|
|
611
653
|
else if (androidMatch) {
|
|
612
654
|
if (!handlers.android)
|
|
@@ -623,7 +665,7 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
623
665
|
continue;
|
|
624
666
|
}
|
|
625
667
|
handlers.computer(logical, url.searchParams.get('host') ?? '', {
|
|
626
|
-
protocol:
|
|
668
|
+
protocol: computerTarget.protocol, port: String(computerTarget.port), readOnly: !full(subjectId) && computerResource(resources(subjectId), computerTarget.host, computerTarget.protocol, computerTarget.port)?.access !== 'control',
|
|
627
669
|
});
|
|
628
670
|
}
|
|
629
671
|
else
|
|
@@ -635,14 +677,14 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
635
677
|
throw new Error('INVALID_STREAM');
|
|
636
678
|
const data = JSON.parse(packet.data);
|
|
637
679
|
if (item.computer) {
|
|
638
|
-
if (!full(subjectId))
|
|
680
|
+
if (!item.computerTarget || !computerAllowed(subjectId, item.computerTarget) || (item.computerControl && !full(subjectId) && !resources(subjectId).computers?.some(c => c.host.toLowerCase() === item.computerTarget.host.toLowerCase() && c.protocol === item.computerTarget.protocol && c.port === item.computerTarget.port && c.access === 'control')))
|
|
639
681
|
throw new Error('AUTHORIZATION_DENIED');
|
|
640
682
|
const types = item.computerProtocol === 'rdp' ? ['start', 'instruction', 'ack'] : ['data', 'ack'];
|
|
641
683
|
if (!types.includes(data?.type))
|
|
642
684
|
throw new Error('ACTION_NOT_ALLOWED');
|
|
643
685
|
}
|
|
644
686
|
else if (item.android) {
|
|
645
|
-
if (!
|
|
687
|
+
if (!androidAllowed(subjectId, item.android, data?.type === 'control' && data?.data !== 'EQ=='))
|
|
646
688
|
throw new Error('AUTHORIZATION_DENIED');
|
|
647
689
|
if (!['control', 'ack', 'ping', 'bitrate'].includes(data?.type))
|
|
648
690
|
throw new Error('ACTION_NOT_ALLOWED');
|
|
@@ -678,6 +720,7 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
678
720
|
duplex.close?.(new Error('Secure channel closed'));
|
|
679
721
|
}
|
|
680
722
|
finally {
|
|
723
|
+
sharingPresence.close(presenceKey);
|
|
681
724
|
unregisterCollaboration?.();
|
|
682
725
|
collaborationRpc?.close();
|
|
683
726
|
clearInterval(revokeTimer);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export class SessionSharingPresence {
|
|
2
|
+
connections = new Map();
|
|
3
|
+
visit(connection, device, session) {
|
|
4
|
+
const current = this.connections.get(connection) || { device, sessions: new Map() };
|
|
5
|
+
current.sessions.set(session, Date.now());
|
|
6
|
+
this.connections.set(connection, current);
|
|
7
|
+
}
|
|
8
|
+
streams = new Map();
|
|
9
|
+
openStream(key, connection, device, session) { this.streams.set(key, { connection, device, session }); }
|
|
10
|
+
closeStream(key) { this.streams.delete(key); }
|
|
11
|
+
close(connection) { this.connections.delete(connection); for (const [key, stream] of this.streams)
|
|
12
|
+
if (stream.connection === connection)
|
|
13
|
+
this.streams.delete(key); }
|
|
14
|
+
summary(grants, invitations, normalize) {
|
|
15
|
+
const result = new Map();
|
|
16
|
+
const record = (id) => { const key = normalize(id); let item = result.get(key); if (!item) {
|
|
17
|
+
item = { sessionId: key, devices: new Set(), invitations: 0 };
|
|
18
|
+
result.set(key, item);
|
|
19
|
+
} return item; };
|
|
20
|
+
for (const grant of grants)
|
|
21
|
+
if (grant.scope.kind === 'sessions')
|
|
22
|
+
for (const id of grant.scope.sessionIds)
|
|
23
|
+
record(id).devices.add(grant.subjectId);
|
|
24
|
+
for (const invite of invitations)
|
|
25
|
+
if (invite.scope.kind === 'sessions')
|
|
26
|
+
for (const id of invite.scope.sessionIds)
|
|
27
|
+
record(id).invitations++;
|
|
28
|
+
return [...result.values()].map(item => {
|
|
29
|
+
const online = new Set();
|
|
30
|
+
for (const connection of this.connections.values())
|
|
31
|
+
if (Date.now() - (connection.sessions.get(item.sessionId) || 0) < 15_000 && item.devices.has(connection.device))
|
|
32
|
+
online.add(connection.device);
|
|
33
|
+
for (const stream of this.streams.values())
|
|
34
|
+
if (stream.session === item.sessionId && item.devices.has(stream.device))
|
|
35
|
+
online.add(stream.device);
|
|
36
|
+
return { sessionId: item.sessionId, devices: item.devices.size, connectedDevices: online.size, invitations: item.invitations };
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|